解决这个问题的一种方法是使用WhenAll。
以一个示例来说明,我创建了X个名称为FuncX()的方法,如下所示:
async static Task<int> FuncX()
{
await Task.Delay(500);
var result = await Task.FromResult(1);
return result;
}
var task1 = new List<Task<int>>();
task1.Add(Func3());
task1.Add(Func4());
var task2 = new List<Task<int>>();
task2.Add(Func1());
task2.Add(Func5());
task2.Add(Func6());
// option1
var eachFunctionIsDoneWithAwait1 = await Task.WhenAll(task1);
var eachFunctionIsDoneWithAwait2 = await Task.WhenAll(task2);
var sum1 = eachFunctionIsDoneWithAwait1.Sum() + eachFunctionIsDoneWithAwait2.Sum();
Console.WriteLine(sum1);
// option2
var task3 = new List<List<Task<int>>>();
task3.Add(task1);
task3.Add(task2);
var sum2 = 0;
task3.ForEach(async x =>
{
var r = await Task.WhenAll(x);
sum2 += r.Sum();
});
Console.WriteLine(sum2);
这只是一个启示性的例子,你可以根据自己的需求进行更改。
以下是如何使用 Task.Run
方法根据图表创建任务的方法:
Task task1 = Task.Run(() =>
{
Fun3();
Fun4();
});
Task task2 = Task.Run(() =>
{
Fun1();
Fun5();
Fun6();
});
Task task3 = Task.Run(async () =>
{
await Task.WhenAll(task1, task2);
Fun7();
Fun8();
});
Task.Run
会在 ThreadPool
上调用委托,而不是在专用线程上调用。如果您有某些原因需要为每个任务创建专用线程,则可以使用高级的 Task.Factory.StartNew
方法,并使用 TaskCreationOptions.LongRunning
参数,如 这里 所示。
Fun3()
立即失败,最优行为应该尽快停止task2
的执行。但是,这个实现将在传播错误之前执行所有三个函数Fun1
、Fun5
和Fun6
。你可以通过创建一个CancellationTokenSource
并在每个函数后调用Token.ThrowIfCancellationRequested
来修复这个小缺陷,但这会很麻烦。task1
和task2
都失败了,只有task1
的异常会通过task3
传播。解决这个问题并不是简单的。
更新:这里有一种解决部分异常传播问题的方法:
Task task3 = Task.WhenAll(task1, task2).ContinueWith(t =>
{
if (t.IsFaulted)
{
TaskCompletionSource tcs = new();
tcs.SetException(t.Exception.InnerExceptions);
return tcs.Task;
}
if (t.IsCanceled)
{
TaskCompletionSource tcs = new();
tcs.SetCanceled(new TaskCanceledException(t).CancellationToken);
return tcs.Task;
}
Debug.Assert(t.IsCompletedSuccessfully);
Fun7();
Fun8();
return Task.CompletedTask;
}, default, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default)
.Unwrap();
task1
和task2
都失败了,task3
将传播这两个任务的异常。Task.WhenAll()
只会抛出来自 task1
的异常。根据 MSDN 文档 - 如果任何一个提供的任务以故障状态完成,则返回的任务也将以 Faulted 状态完成,其中其异常将包含来自每个提供的任务的未包装异常集合的聚合- 那么它不应该也抛出来自 task2
的异常吗? - Qwertyluk
Fun4
应该在Fun3
完成后运行,还是与Fun3
并发运行? - Theodor Zoulias