我有一个多线程表单应用程序,这就是相关部分的设计方式:
线程 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 个都设置完毕。此时所有 PrimaryLoader 都到达 _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() 时,在 _endEvent.WaitOne() 处没有释放 PrimaryLoader;你可以看到我签入了 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
}
}