ThreadPool和委托操作-WaitHandle.WaitAll()
本文关键字:WaitAll -WaitHandle 操作 ThreadPool | 更新日期: 2023-09-27 18:28:23
我想在ThreadPool
中启动几次简单的委托操作。问题是我需要等待所有的行动完成。如何处理?
Action<int> someAction = i => { /* do something */ }
foreach (var yIndex in yRange)
{
foreach (var xIndex in xRange)
{
// ThreadPool.QueueUserWorkItem(??? someAction(yIndex)) - how to start someAction(int) in a thread pool
}
WaitHandle.WaitAll(doneEvents); // how to wait for to finish?
}
我知道我可以使用一个新类并创建一个ManualResetEvent
回调。但问题是someAction(int)
使用了许多在主类中实现的方法。。。
您可以使用任务并行库:
List<Action> actions = new List<Action>();
foreach(var yIndex in yRange)
foreach(var xIndex in xRange)
actions.Add(() => someAction(yIndex));
Parallel.Invoke(actions.ToArray());
Parallel.Invoke在所有任务完成后返回,并在可能的情况下并行运行它们(就像线程池一样)。
采纳Matten的建议并使用TPL机制。TPL有很多不同的有效方法。
如果你想知道这是如何使用老式方法完成的,那么继续阅读。一般模式是使用单一的信号机制。为每个操作创建一个WaitHandle
根本不可扩展。事实上,WaitHandle.WaitAll
方法无论如何只接受64个句柄。
foreach (var yIndex in yRange)
{
var yclosure = yIndex;
var finished = new CountdownEvent(1);
foreach (var xIndex in xRange)
{
var xclosure = xIndex;
finished.AddCount();
ThreadPool.QueueUserWorkItem(
(state) =>
{
try
{
DoSomething(yclosure, xclosure);
}
finally
{
finished.Signal();
}
}, null);
}
finished.Signal();
finished.Wait();
}
上面的模式使用CountdownEvent
作为信令机制。您也可以使用int
计数器和Interlocked
方法。人们在使用这种模式时会犯两个常见的错误。
您应该像对待其他线程一样,将主线程视为并行任务。这意味着我们需要用1个计数初始化信号(表示主线程)。然后我们发出信号,表明主线程在循环结束时通过
Signal
完成了任务的排队。如果你没有遵循这一建议,那么如果一项任务在下一项任务排队之前完成,就有可能出现非常微妙的竞争情况1必须为所需的任何循环变量的闭包创建单独的变量在lambda表达式或匿名委托中使用。请记住,闭包捕获变量而不是值,因此如果不使用特殊的捕获变量,任务可能无法使用您认为的值。
1在这种特殊情况下,如果事件已经用信号通知,AddCount
实际上会抛出异常
我认为您可以使用Semaphore对象来实现您的目标。试试这个:
int numActions = xRange.Count * yRange.Count; // Set the total number of actions
Semaphore resCount = new Semaphore(0, numActions);
Action<int> someAction = i => { try {/* do something */} finally {resCount.Release(1);} }
// ... the big loop adding actions
resCount.WaitOne();
代码并不精确,但这个想法应该可行。