我建议使用 async/await 和 CancellationTokenSources。我也会提供有关如何使用 BackgroundWorkers 的建议,但由于 async/await 更方便(而且更短),所以它先行。
async/await 很方便,因为它可以为您提供您正在寻找的功能,而不会增加太多复杂性。
private async void SomeEvent(object sender, EventArgs e)
{
CancellationTokenSource cts = new CancellationTokenSource();
var waiter1 = DoSomething(cts.Token);
var waiter2 = DoSomethingElse(cts.Token);
// etc.
// Wait for the first one to finish, then cancel
await Task.WhenAny(waiter1, waiter2, ...).ConfigureAwait(false);
cts.Cancel();
// wait for the remainder to finish
await Task.WhenAll(waiter1, waiter2, ...).ConfigureAwait(false);
// Do Postprocessing
}
您的“服务员”看起来像这样:
private async Task DoSomething(CancellationToken token)
{
// Do stuff
// Periodically check if someone has finished
if (Token.IsCancellationRequested)
{
// clean up
return;
}
}
async/await 代码有一些陷阱,including deadlock。因为这听起来像是一个快速的项目(我可能错了),所以它似乎是一个学习的好地方——尤其是在没有大量代码库需要返工的情况下。如果您想了解更多信息,我认为 Stephen Cleary 的博客是一个不错的起点,尤其是他的 intro。
另一方面,如果你绝对确定要使用 BackgroundWorkers……好吧,我不怪你,但我也不羡慕你。
首先,您的员工必须知道其他人是否先完成。使用完成的BackgroundWorker的RunWorkerCompleted方法取消其他BackgroundWorkers:
private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
// Check for errors...
}
else if (e.Cancelled)
{
// Mark that this one has finished
}
else
{
// Assuming you have a set of BackgroundWorkers called "workers"
foreach (var bgw in workers)
bgw.CancelAsync();
// other stuff...
}
}
然后,在 DoWork 方法的末尾添加一些代码来报告取消...
private void DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
// Do stuff...
// When "RunWorkerCompleted" is called, let it know whether this worker has been cancelled.
e.Cancel = worker.CancellationPending;
}
就是这样。您还可以定期检查worker.CancellationPending,看看您是否可以提前完成,但不要忘记在您返回之前将worker.CancellationPending 分配给e.Cancel!
最后一件事:如果您希望在所有工作人员完成后(并且仅在那时)继续进行后处理,您需要有一种方法来标记特定工作人员何时完成(以取消其他工作人员),然后有一种方法找出它们何时完成(以便您可以开始后处理)。这是可行的,而且不太难——在我的脑海中,我会使用Dictionary<BackgroundWorker, bool> 来指示哪些工人已经完成。不过,这是你可以通过 async/await 避免的另一块混乱。