У меня есть приложение с многопоточными формами, и вот как спроектирована соответствующая деталь:
Поток 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
}
}