У меня есть многопоточное приложение формы, и вот как спроектирована рассматриваемая часть:
Поток 2 (класс BatchPreviewAssistant) ожидает, пока основной поток интерфейса передаст задачу загрузки изображений. После получения задачи BatchPreviewAssistant назначает задачи N=5 ожидающим потокам PrimaryLoader и включает их. PrimaryLoaders работают как бесконечные циклы, запускаемые/останавливаемые с использованием двух событий ручного сброса: _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.WaitOne( ) и подождите. Теперь все _parentSyncEvents установлены, что позволяет BatchPreviewAssistant продолжать работу. BatchPreviewAssistant сбрасывает _startEvent, а затем устанавливает _endEvent, который освобождает PrimaryLoaders, и они возвращаются к началу цикла.
Помощник по пакетному просмотру:
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());
}
Основной загрузчик:
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
}
}