ManualResetEvent - WaitOne (), похоже, не освобождает поток в какой-то момент - PullRequest
0 голосов
/ 29 октября 2011

У меня есть приложение с многопоточными формами, и вот как спроектирована соответствующая деталь:

Поток 2 (класс BatchPreviewAssistant) ожидает, когда основной поток интерфейса пройдет задачу загрузки изображений.Как только задача получена, BatchPreviewAssistant назначает задачи N = 5 ожидающим потокам PrimaryLoader и включает их.PrimaryLoaders работают как бесконечные циклы, запускаемые / останавливаемые с использованием 2 событий ручного сброса: _startEvent и _endEvent.Кроме того, имеется массив из N событий ручного сброса _parentSyncEvent, сигнализирующих об окончании обработки от PrimaryLoaders к BatchPreviewAssistant.

Так что обычно каждый PrimaryLoader ожидает _startEvent.WaitOne ().Как только BatchPreviewAssistant должен активировать их и запустить RunPrimaryLoaders (), он сначала сбрасывает _endEvent и _parentSyncEvents, а затем устанавливает _startEvent.Теперь он блокируется в WaitHandle.WaitAll (_parentSyncEvents. _StartEvent.Set () приводит к продолжению работы всех PrimaryLoader. После завершения каждого PrimaryLoader он устанавливает свое собственное событие в _parentSyncEvent до тех пор, пока не будут установлены все 5. На этом этапе все PrimaryLoaders достигают _endEvent.WaitO) и подождите. Теперь все _parentSyncEvents установлены, что позволяет BatchPreviewAssistant продолжать. BatchPreviewAssistant сбрасывает _startEvent, а затем устанавливает _endEvent, который освобождает PrimaryLoaders, и они возвращаются в начало цикла.

BatchPreviewAssistant:

    private void RunPrimaryLoaders()
    {
        BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Debug1, "RunPrimaryLoaders()");
        ResetEvents(_parentSyncEvents);
        _endEvent.Reset();
        _startEvent.Set();

        // Primary Loader loops restart

        BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Debug2, "WaitHandle.WaitAll(_parentSyncEvent");
        if (!WaitHandle.WaitAll(_parentSyncEvents, 20 * 1000))
        {
            throw new TimeoutException("WaitAll(_parentSyncEvent) in ProcessCurrentCommand");
            // TODO: Terminate?
        }
        BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Message3, "Primary loading is complete");
        _startEvent.Reset();
        _endEvent.Set();
        bool isEndEventSet = _endEvent.WaitOne(0);
        BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Debug2, "isEndEventSet?" + isEndEventSet.ToString());
    }
1009 * PrimaryLoader:
    public void StartProc(object arg)
    {
        while (true)
        {
            BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Debug2, "Primary Loader: _startEvent.WaitOne()");
            _startEvent.WaitOne();

            try
            {
                BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Message4, "Primary Loader is processing entry:" + processingEntry.DisplayPosition.ToString());
            }
            catch (Exception ex)
            {
                BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Error, "Exception in PrimaryImageLoader.StartProc:" + ex.ToString());
            }
            _parentSyncEvent.Set();
            BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Debug2, "Primary Loader: _endEvent.WaitOne()");
            _endEvent.WaitOne();
        }
    }

Этот код работает довольно хорошо, создавая сотни таких циклов, но я время от времени сталкиваюсь с проблемой, особенно во время стресс-тестов. Что происходит, когда BatchPreviewAssistant устанавливает _endEvent.Set(), ни один из PrimaryLoaders не выпускается в _endEvent.WaitOne (); вы можете видеть, что я проверяю BatchPreviewAssistant и вижу, что событие действительно установлено, однако PrimaryLoaders не освобождаются.

[10/27/2011;21:24:42.796;INFO ] [42-781:16]Primary Loader: _endEvent.WaitOne()
[10/27/2011;21:24:42.796;INFO ] [42-781:18]Primary Loader: _endEvent.WaitOne()
[10/27/2011;21:24:42.796;INFO ] [42-781:19]Primary Loader: _endEvent.WaitOne()
[10/27/2011;21:24:42.843;INFO ] [42-843:15]Primary Loader: _endEvent.WaitOne()
[10/27/2011;21:24:42.937;INFO ] [42-937:17]Primary Loader: _endEvent.WaitOne()
[10/27/2011;21:24:42.937;INFO ] [42-937:14]Primary loading is complete
[10/27/2011;21:24:42.937;INFO ] [42-937:14]isEndEventSet?True

Есть ли какие-либо очевидные проблемы с таким дизайном, которые могут вызвать проблему?Я могу найти некоторые способы, как это обойти, но было бы неплохо увидеть, что не так с этим подходом.

На всякий случай я также предоставляю информацию о том, как я инициализирую и запускаю PrimaryLoaders.

private PrimaryImageLoader[] _primaryImageLoaders;

_primaryImageLoaders = new PrimaryImageLoader[N]

for (int i = 0; i < _primaryImageLoaderThreads.Length; i++)
{
  _parentSyncEvents[i] = new AutoResetEvent(false);
  _primaryImageLoaders[i] = new PrimaryImageLoader(i, _parentSyncEvents[i], 
      _startEvent, _endEvent,
      _pictureBoxes, _asyncOperation,
      LargeImagesBufferCount);
  _primaryImageLoaderThreads[i] = new Thread(new ParameterizedThreadStart(_primaryImageLoaders[i].StartProc));
  _primaryImageLoaderThreads[i].Start();
}

Обратите внимание, что для упрощения образца был удален какой-то нерелевантный код.

ДОБАВЛЕНО: Я согласен, что образец слишком занят и за ним трудно следовать.Так вот в двух словах:

Thread 2:
private void RunPrimaryLoaders()
{
  _endEvent.Reset();
  _startEvent.Set();

  _startEvent.Reset();
  _endEvent.Set();
  bool isEndEventSet = _endEvent.WaitOne(0);
}

Threads 3-7:
public void StartProc(object arg)
{
  while (true)
  {
    _startEvent.WaitOne();

    _endEvent.WaitOne();     // This is where it can't release occasionally although Thread 2 checks and logs that the event is set
  }
}

Ответы [ 2 ]

2 голосов
/ 29 октября 2011

Есть ли какие-либо очевидные проблемы с таким дизайном, которые могут вызвать проблему?

Похоже, у вас очень сложный дизайн, когда вы пытаетесь сделать что-то простое.Кажется, что простой шаблон Producer / Consumer будет работать намного лучше, и вам не придется иметь дело с этим бедствием событий ручного сброса.

Возможно, вы хотите что-то еще в этом духе:*

Таким образом, вам придется запускать каждый экземпляр Producer в отдельном потоке и каждый экземпляр Consumer в отдельном потоке.Конечно, вы должны добавить все навороты, чтобы завершить их изящно, но это другая история.

0 голосов
/ 29 октября 2011

У вас есть состояние гонки.Если ваша логика заключается в том, что вы обнаруживаете условие, устанавливаете событие для блокировки, а затем ждете события, должна быть промежуточная разблокировка.

Ваш код делает это:

  1. Решите подождать

  2. Установить событие для блокировки

  3. Ожидать событие

Проблема возникает, если событие происходит между шагами 1 и 2. Событие, возможно, уже произошло и разблокировало событие, когда мы установили событие для блокировки.Когда мы переходим к шагу 3, мы ожидаем события, которое уже произошло, чтобы разблокировать объект, который он уже разблокировал.Плохо.

Исправление выглядит следующим образом:

  1. Получение блокировки

  2. Нужно ли ждать?Если нет, снимите блокировку и верните

  3. Установить событие на блокировку

  4. Снять блокировку

  5. Ожиданиеon event

Поскольку мы удерживаем блокировку сейчас, событие не может произойти между тем, когда мы решаем ждать и когда мы устанавливаем событие для блокировки.Код, который разблокирует событие, должен, конечно, иметь такую ​​же блокировку, как и при прохождении логики обработки события и разблокирования события.

...