我有两个任务。我用 Task.WhenAll 运行它们。如果其中一个任务抛出异常,另一个任务是否会继续执行完成呢?
我有两个任务。我用 Task.WhenAll 运行它们。如果其中一个任务抛出异常,另一个任务是否会继续执行完成呢?
只需运行此代码以进行测试:
private static async Task TestTaskWhenAll()
{
try
{
await Task.WhenAll(
ShortOperationAsync(),
LongOperationAsync()
);
}
catch (Exception exception)
{
Console.WriteLine(exception.Message); // Short operation exception
Debugger.Break();
}
}
private static async Task ShortOperationAsync()
{
await Task.Delay(1000);
throw new InvalidTimeZoneException("Short operation exception");
}
private static async Task LongOperationAsync()
{
await Task.Delay(5000);
throw new ArgumentException("Long operation exception");
}
调试器将在5秒钟后停止。两个异常都被抛出,但是Debugger.Break()
只被触发了一次。更重要的是,exception
值不是AggregateException
,而是InvalidTimeZoneException
。这是由于新的async/await
会展开为实际异常。您可以在此处阅读更多内容。如果您想阅读其他Exceptions
(不仅仅是第一个),您需要从WhenAll
方法调用返回的Task
中读取它们。
Task.Run
对于已经是异步方法的目的是什么? - FabioThread.Sleep(1000)
比 Task.Delay(1000).Wait()
更简单。 - Theodor Zoulias如果另一个任务失败了,它不会被停止。
Task.When
会等待所有任务完成,无论是否有任务失败。我刚刚进行了测试以验证 - 它花费了5秒钟才完成:
Task allTasks = Task.WhenAll(getClientToken, getVault, Task.Delay(5000));
Task allTasks = Task.WhenAll(getClientToken, getVault, Task.Delay(5000));
try
{
await allTasks;
} catch (Exception ex)
{
// ex is the 'unwrapped' actual exception
// I'm not actually sure if it's the first task to fail, or the first in the list that failed
// Handle all if needed
Exceptions[] allExceptions = allTasks.Exceptions;
// OR
// just get the result from the task / exception
if (getVault.Status == TaskStatus.Faulted)
{
...
}
}
我有同样的问题并进行了测试,简而言之:
它始终等待所有任务完成。
如果在所有任务完成后还有异常,将抛出第一个异常(如果不捕获将导致崩溃)。
对于 所有 异常,请保留由 Task.WhenAll
返回的 Task
实例,并使用 Exception.InnerExceptions
属性。
这是我的测试:
static async Task Main(string[] args)
{
var tasks = new[] { Foo1(), Foo2(), Foo3() };
Task t = null;
try
{
t = Task.WhenAll(tasks);
await t;
}
catch (Exception ex)
{
Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}
Console.WriteLine("All have run.");
if (t.Exception != null)
{
foreach (var ex in t.Exception.InnerExceptions)
{
Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}
}
}
static async Task Foo1()
{
await Task.Delay(50);
throw new ArgumentException("zzz");
}
static async Task Foo2()
{
await Task.Delay(1000);
Console.WriteLine("Foo 2");
throw new FieldAccessException("xxx");
}
static async Task Foo3()
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(200);
Console.WriteLine("Foo 3");
}
}
输出:
Foo 3
Foo 3
Foo 3
Foo 3
Foo 2
Foo 3
Foo 3
Foo 3
Foo 3
Foo 3
Foo 3
ArgumentException: zzz
All have run.
ArgumentException: zzz
FieldAccessException: xxx
Task t = null;
初始化任务容易出错。使用 Task t;
更安全,因为它强制你在 try 块内初始化变量,否则你的代码将无法编译。还要注意,在大多数情况下,t.Exception
将为 null
(因为希望任务大部分时间都能成功完成)。因此,你当前的代码将在成功的情况下导致 NullReferenceException
。 - Theodor ZouliasTask t;
,它将无法编译,因为t
的赋值在try
块内部。是的,我应该为t.Exception
添加一个空值检查,谢谢! - Luke VoTask t = Task.WhenAll(tasks);
否则,你也应该检查t == null
的情况以确保100%的安全性,这很容易被忽略。 - Theodor ZouliasIEnumerable
懒加载。你的代码当前通过调用在定义任务数组时运行/调度了3个任务,然后--使用Task.WhenAll
--确保那些在执行流中正在运行的任务完成。相反,让Task.WhenAll
执行可运行任务; var runnableTasks = new [] { Foo1, Foo2, Foo3}.Select(async (fn) => await fn())
,然后var t = Task.WhenAll(runnableTasks);
- Brett Caswellvar tasks = new[] { Foo1(), Foo2(), Foo3() };
后面添加 await Task.Delay(10000);
,我相信你会发现它们正在运行。 - Brett Caswell
WhenAll
实现感兴趣,这里有一个相关的问题。 - Theodor Zoulias