【发布时间】:2009-06-24 15:10:45
【问题描述】:
我在多种编程语言中都遇到过这个问题,我只是想知道处理它的最佳方法是什么。
我有三个异步触发的方法调用。每个都有一个回调。我只想在所有三个回调都完成后才做某事。
编写此代码的最佳方法是什么?我通常会使用所有这些公共 bool 标志,当您添加更多调用时,代码会变得更加复杂。
【问题讨论】:
我在多种编程语言中都遇到过这个问题,我只是想知道处理它的最佳方法是什么。
我有三个异步触发的方法调用。每个都有一个回调。我只想在所有三个回调都完成后才做某事。
编写此代码的最佳方法是什么?我通常会使用所有这些公共 bool 标志,当您添加更多调用时,代码会变得更加复杂。
【问题讨论】:
来自 C#,我可能会使用 WaitHandle.WaitAll。您可以创建一个ManualResetEvent 对象数组(每个要完成的任务一个),然后将该数组传递给WaitAll。线程化的任务将分别获得一个 ManualResetEvent 对象,并在准备就绪时调用Set 方法。 WaitAll 将阻塞调用线程,直到所有任务完成。我将给出一个 C# 代码示例:
private void SpawnWorkers()
{
ManualResetEvent[] resetEvents = new[] {
new ManualResetEvent(false),
new ManualResetEvent(false)
};
// spawn the workers from a separate thread, so that
// the WaitAll call does not block the main thread
ThreadPool.QueueUserWorkItem((state) =>
{
ThreadPool.QueueUserWorkItem(Worker1, resetEvents[0]);
ThreadPool.QueueUserWorkItem(Worker2, resetEvents[1]);
WaitHandle.WaitAll(resetEvents);
this.BeginInvoke(new Action(AllTasksAreDone));
});
}
private void AllTasksAreDone()
{
// OK, all are done, act accordingly
}
private void Worker1(object state)
{
// do work, and then signal the waiting thread
((ManualResetEvent) state).Set();
}
private void Worker2(object state)
{
// do work, and then signal the waiting thread
((ManualResetEvent)state).Set();
}
请注意,AllTasksAreDone 方法将在用于生成工作线程的线程池线程上执行,而不是在主线程上执行...我假设许多其他语言都有类似的构造。
【讨论】:
如果您真的只想等待全部完成:
【讨论】:
使用semaphore。
【讨论】:
Future 非常易于使用。 Futures 看起来像普通函数,除了它们执行异步。
课程:
public struct FutureResult<T>
{
public T Value;
public Exception Error;
}
public class Future<T>
{
public delegate R FutureDelegate<R>();
public Future(FutureDelegate<T> del)
{
_del = del;
_result = del.BeginInvoke(null, null);
}
private FutureDelegate<T> _del;
private IAsyncResult _result;
private T _persistedValue;
private bool _hasValue = false;
private T Value
{
get
{
if (!_hasValue)
{
if (!_result.IsCompleted)
_result.AsyncWaitHandle.WaitOne();
_persistedValue = _del.EndInvoke(_result);
_hasValue = true;
}
return _persistedValue;
}
}
public static implicit operator T(Future<T> f)
{
return f.Value;
}
}
这里我使用futures来模拟死锁:
void SimulateDeadlock()
{
Future> deadlockFuture1 = new Future>(() =>
{
try
{
new SystemData(ConfigurationManager.ConnectionStrings["DbConnectionString"].ConnectionString)
.SimulateDeadlock1(new DateTime(2000, 1, 1, 0, 0, 2));
return new FutureResult { Value = true };
}
catch (Exception ex)
{
return new FutureResult { Value = false, Error = ex };
}
});
Future> deadlockFuture2 = new Future>(() =>
{
try
{
new SystemData(ConfigurationManager.ConnectionStrings["DbConnectionString"].ConnectionString)
.SimulateDeadlock2(new DateTime(2000, 1, 1, 0, 0, 2));
return new FutureResult { Value = true };
}
catch (Exception ex)
{
return new FutureResult { Value = false, Error = ex };
}
});
FutureResult result1 = deadlockFuture1;
FutureResult result2 = deadlockFuture2;
if (result1.Error != null)
{
if (result1.Error is SqlException && ((SqlException)result1.Error).Number == 1205)
Console.WriteLine("Deadlock!");
else
Console.WriteLine(result1.Error.ToString());
}
else if (result2.Error != null)
{
if (result2.Error is SqlException && ((SqlException)result2.Error).Number == 1205)
Console.WriteLine("Deadlock!");
else
Console.WriteLine(result2.Error.ToString());
}
}
【讨论】:
对于那些使用 JavaScript 的人,请考虑使用在 Stackoverflow 问题中讨论的模式: javascript: execute a bunch of asynchronous method with one callback
【讨论】: