очень странная и серьезная проблема несогласованности многопоточности С#

У меня есть очень простая сторожевая программа с двумя потоками. Один поток обновляет длинную переменную, а другой поток читает эту переменную. и предупреждать, если прошло более X секунд с момента последнего обновления. Проблема в том, что иногда (случается раз в день более или менее) второй поток считывает устаревшее значение переменной.

Иногда это устаревшее значение 3 секунды назад (т.е. первый поток обновил длинную переменную, но через 3 секунды другой поток не получил новое значение)

Я использую блокировку, чтобы избежать проблемы с многопоточным кэшированием. Я также пробовал Volatile, Interlock, volatileRead и т. д., но ничего не помогает. Класс инициируется через программу VB 6 через COM. Программа очень проста, поэтому я думаю, что это ошибка в C# (возможно, связанная с COM). это программа:

Можете ли вы помочь, пожалуйста?

public class WatchDog
{
    long lastDate = DateTime.Now.ToBinary();

    private object dateLock = new object();
    bool WatchdogActive = true;
    int WatchdogTimeoutAlert = 5;
    int WatchdogCheckInterval = 6000;

    private void WatchdogThread()
    {
        try
        {
            while (WatchdogActive)
            {
                lock (dateLock)
                {
                    DateTime lastHB = DateTime.FromBinary(lastDate);

                    if ((DateTime.Now.Subtract(lastHB).TotalSeconds > WatchdogTimeoutAlert))
                    {
                        Console.WriteLine(" last Date is " + lastDate);

                    }
                }
                Thread.Sleep(WatchdogCheckInterval);
            }
        }
        catch (Exception Ex)
        {
        }
    }

    private void OnHeartbeatArrive(long heartbeatTime)
    {
        lock (dateLock)
        {
            lastDate = heartbeatTime;
            Console.WriteLine(" Got Heartbeat lastDate " + lastDate);
        }
    }
}

person ofer alper    schedule 27.12.2010    source источник
comment
Возможно, вам потребуется предоставить некоторую информацию о том, как вызывается OnHeartbeatArrive() и, в частности, как определяется переданный ему heartbeatTime.   -  person Michael Burr    schedule 27.12.2010
comment
Чтобы было ясно, демонстрирует ли опубликованный код с операторами WriteLine() проблему?   -  person Henk Holterman    schedule 27.12.2010
comment
@michael Когда я получаю сообщение с сервера по TCP, я вызываю OnHeartbeatArrive с DateTime.Now.ToBinary(). Обратите внимание, что я печатаю ту же переменную. Я вижу, что он изменил свое значение из потока обновления, но я вижу старое значение в потоке чтения.   -  person ofer alper    schedule 27.12.2010
comment
@Хенк: Да. я использовал консоль writeLine, чтобы увидеть значение переменной после обновления и чтения, а затем я заметил, что поток чтения считывает старое значение даже 3 секунды! после того, как поток обновления обновил значение и распечатал его.   -  person ofer alper    schedule 27.12.2010
comment
Какую IDE вы используете. Вы запускаете программу из IDE?   -  person Lorenz Lo Sauer    schedule 09.09.2012


Ответы (2)


        while (WatchdogActive)

Это не работает, WatchdogActive не объявлен volatile. В сборке Release переменная, скорее всего, будет сохранена в регистре ЦП, она никогда не увидит обновление, которое какой-то другой поток вносит в переменную. Другими словами, сторожевой таймер будет по-прежнему активен, даже если вы его отключили.

Вы должны использовать здесь ManualResetEvent, его метод WaitOne(int) автоматически позаботится о Sleep() и даст вам гораздо более быстрое завершение потока в качестве бонуса.

Какие-то странные нестыковки. Вы указываете сбой через 3 секунды, но проверяете только >= 5 секунд. Sleep() длиннее проверки, что позволяет пропустить сбои. Кажется, вам нравятся пустые блоки catch, которые всегда дают большие возможности для того, чтобы код не работал без какой-либо диагностики. Я предполагаю, что мы не смотрим на настоящий код, из-за чего трудно увидеть тонкие проблемы с потоками. Работайте, исходя из предположения, что это не ошибка в C#.

person Hans Passant    schedule 27.12.2010
comment
конечно, это возможно. Поток чтения действительно проверяет каждые 5 секунд. Но поток выглядит следующим образом: поток обновления обновляет значение в моменты времени X -2 и X. Поток чтения просыпается в момент X + 3, но не получает самого обновленного значения. Получается значение X - 2. - person ofer alper; 27.12.2010
comment
конечно, это возможно. Поток чтения действительно проверяет каждые 5 секунд. Но поток выглядит следующим образом: поток обновления обновляет значение в моменты времени X -2 и X. Поток чтения просыпается в момент X + 3, но не получает самого обновленного значения. Он получает значение X - 2. Код почти идентичен реальному коду, как я уже сказал, он очень прост. Не уверен, что вы имели в виду, что WatchdogActive не является изменчивым. В большинстве случаев это работает нормально, но это происходит раз в день. Я буду рад узнать, что это не ошибка, связанная с С# com, но у меня заканчиваются идеи. - person ofer alper; 27.12.2010
comment
Откуда ты это знаешь? Вы весь день смотрите в окно консоли, чтобы увидеть X? Или это действительно регистрируется? Регистраторы имеют собственную блокировку. Сделайте фрагмент кода точной копией реального кода. - person Hans Passant; 27.12.2010
comment
Я печатаю все время, но я не смотрю его весь день :) . Триггер — это предупреждение, которое я получаю от сторожевого таймера. Когда я получаю оповещения, я вижу все отпечатки (все обновления) и я вижу отпечаток потока чтения. Да, я использую регистратор, но я не понимаю, как он может объяснить вышеизложенное. Время регистратора находится в точной корреляции с DateTime.Now. Также это происходит через 3 секунды, поэтому вряд ли это проблема регистратора. - person ofer alper; 27.12.2010

обычно я использую lock() для изменчивого объекта, который находится на «левой стороне», в этом случае используйте

volatile object lastDate = DateTime.Now.ToBinary();
...
lock(lastDate){...}

И почему вы передаете «длинный» вместо DateTime?

person Bonshington    schedule 27.12.2010
comment
Попробуйте: volatile недействительно для типа long... - person Henk Holterman; 27.12.2010
comment
пробовал все варианты. Я пробовал работать с volatile + lock, пробовал работать с DateTime, пробовал читать и писать с VolatileRead, пробовал читать и писать с Interlock. Ничего не сработало. На самом деле я начал с DateTime, я изменил его на длинный, чтобы убедиться, что это не связано с проблемой DateTime. - person ofer alper; 27.12.2010
comment
возможно, используйте Stack‹DateTime›. для проверки используйте .Peek() и не забудьте очистить стек, чтобы освободить ресурсы - person Bonshington; 24.01.2011