ManualResetEvent - как здесь может возникнуть состояние гонки?

Я пытаюсь определить, могу ли я использовать ManualResetEvent здесь, чтобы гарантировать, что в параллельной среде, внутренние действия myMethod() никогда не вызываются одновременно.

    static volatile bool _syncInitialized = false;  

    static ManualResetEvent _syncEvent = new ManualResetEvent(false);

    static object _syncLock = new object();

    void myMethod()
    {
        lock (_syncLock)       
        {
            if (!_syncInitialized)          // sync hasn't started, so 
            {
                _syncInitialized = true;    
                _syncEvent.Set();           // signal for the first time only 
            }
        }

        if (_syncEvent.WaitOne())           // wait for signal
        {
            _syncEvent.Close(); 
            _syncEvent = new ManualResetEvent(false); // reset to false 

            // actions that should be forced to run sequentially
        }
    }

РЕДАКТИРОВАТЬ - Обратите внимание, что я использую ManualResetEvent вместо просто lock(), потому что я хочу иметь возможность добавить тайм-аут, потенциально.

1 ответ

Решение

У вас есть хотя бы одна возможность для состояния гонки. Рассматривать:

Поток № 1 выполняет _syncEvent.WaitOne() и успешно, затем выгружается, прежде чем он может выполнить _syncEvent.Close(), Поток #2 приходит и выполняет WaitOne(), а также успешно.

Другая проблема в том, что вы звоните Close() с последующим построением нового экземпляра, по-видимому, в качестве способа сброса. Представьте себе, что вы называете Close(), поток выгружается, следующий поток приходит и пытается сделать WaitOne()и выдает исключение, потому что объект был закрыт.

Если вы хотите сбросить событие, позвоните Reset(),

Вы, вероятно, не можете сделать эту работу с ManualResetEvent, Как уже говорили другие, ManualResetEvent используется для сигнализации, а не взаимного исключения.

Вы говорите, что хотите внедрить таймаут в будущем. Если вы просто хотите, чтобы поток ожидал блокировки в течение некоторого периода времени, а затем завершил работу, если он не может получить блокировку, используйте одну из перегрузок Monitor.TryEnter, которые принимают значение времени ожидания. Например:

private object _syncObject = new Object();
void MyMethod()
{
    if (!Monitor.TryEnter(_syncObject, TimeSpan.FromSeconds(5)))
    {
        return; // couldn't get the lock
    }
    try
    {
        // got the lock. Do stuff here
    }
    finally
    {
        Monitor.Exit(); // release the lock
    }
}

Есть некоторые споры о том, действительно ли вы хотите снять блокировку в finally, Если код выдает исключение, то возможно (вероятно?), Что ресурс, который вы защищали, сейчас находится в неполном или иным образом поврежденном состоянии. В этом случае вы можете не захотеть, чтобы другие потоки воздействовали на него. Снимите ли вы блокировку с учетом исключений - это дизайнерское решение, которое вам придется принять в соответствии с требованиями вашего приложения.

Другие вопросы по тегам