C# - 等待数组中的所有任务完成或失败

C# - Wait For All Tasks In Array To Complete Or Fail

我有一个任务列表,这些任务在不同的数据库上工作,然后在中央数据库上更新一组结果。

如果我无法连接到中央数据库,所需的行为是取消所有任务,等待 运行ning 任务成功停止(因为它是合作的事情)然后退出程序。

我写了几行代码来测试取消任务:

var cancellationTokenSource = new CancellationTokenSource();
var taskList = new List<Task>();
taskList.Add(new Task(() => { Thread.Sleep(5000); }, cancellationTokenSource.Token));
taskList.Add(new Task(() => { Thread.Sleep(5000); }, cancellationTokenSource.Token));
taskList.ForEach(task => task.Start());
cancellationTokenSource.Cancel();
Task.WaitAll(taskList.ToArray());

我知道我通常应该在执行代码的任务中定期检查取消令牌,但现在我只是测试我对取消工作原理的理解。

当我 运行 它时,我得到错误:

您可以将取消令牌传递给 WaitAll,但那是为了实际取消 WaitAll

基本上我只想等到所有任务都 运行 完成或因被取消而停止,这样我就可以安全地退出。

我觉得我可能遗漏了一些简单的东西,如果有人能阐明如何实现这一点,我将不胜感激。

非常感谢

这可能与

重复

基本上你需要的是

Task.WhenAll(taskA, taskB, taskC).Wait()

如上面的回答所述。

首先,避免将阻塞代码(Task.WaitAllWait、...)与非阻塞代码混合; Task.WhenAll 可能是更好的选择。正如 Amogh 的回答 中的 post 所暗示的那样,您可能想改用它:

await Task.WhenAll(taskList.ToArray())
          .ContinueWith(t => { }, TaskContinuationOptions.NotOnRanToCompletion);

例如,下面的代码将打印CanceledRanToCompletionFaulted

var cancellationTokenSource = new CancellationTokenSource();
var taskList = new List<Task>
{
    new Task(() => { Thread.Sleep(1000); }, cancellationTokenSource.Token),
    new Task(() => { Thread.Sleep(5000); }),
    new Task(() =>
    {
        Thread.Sleep(3000);
        throw new InvalidOperationException();
    })
};

taskList.ForEach(task => task.Start());
cancellationTokenSource.Cancel();

await Task.WhenAll(taskList.ToArray())
          .ContinueWith(t => { }, TaskContinuationOptions.NotOnRanToCompletion);

Console.WriteLine(taskList[0].Status);
Console.WriteLine(taskList[1].Status);
Console.WriteLine(taskList[2].Status);

也就是说,这只有在您打算之后观察各个任务时才有意义。如果您不这样做,请接受例外情况。

TaskCanceledException 是一个异常,在您取消 运行 任务时总是抛出该异常。您只需要使用 try-catch 块来处理它。这违背了微软自己的哲学,即尽可能避免抛出异常 (Best Practices For Exceptions).

最简单的解决方案如下所示:

var cancellationTokenSource = new CancellationTokenSource();
var taskList = new List<Task>
{
    Task.Run(() => { Thread.Sleep(1); }),
    Task.Run(() => { Thread.Sleep(5000); }, cancellationTokenSource.Token),
    Task.Run(() => { Thread.Sleep(5000); }, cancellationTokenSource.Token)
};

cancellationTokenSource.Cancel();

try
{
    Task.WhenAll(taskList).GetAwaiter().GetResult();
}
catch (TaskCanceledException)
{
    Console.WriteLine("Tasks were cancelled");;
}

Console.WriteLine(taskList[0].Status);
Console.WriteLine(taskList[1].Status);
Console.WriteLine(taskList[2].Status);