并发编程 --- 异步方法的异常处理

引言

现在模拟一个异步方法抛出了异常:

1
2
3
4
5
public static async Task ThrowAfter(int ms, string message)
{
await Task.Delay(ms);
throw new Exception(message);
}

思考一下, DontHandle() 方法是否能够捕获到异常?

1
2
3
4
5
6
7
8
9
10
11
12
public static void DontHandle()
{
try
{
ThrowAfter(1000, "first");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}

}

答案是:不会捕获到异常!

因为 DontHandle() 方法在 ThrowAfter() 方法抛出异常之前,就已经执行完毕。

异步方法的异常处理

那么上述代码怎么才能捕获到异常呢?

若想要捕获异常则必须通过 await 关键字等待 ThrowAfter() 方法执行完成。

将上文中的代码段进行修改:

1
2
3
4
5
6
7
8
9
10
11
public static async void HandleoOnError()
{
try
{
await ThrowAfter(1000, "first");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}

结果就会输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static async void StartTwoTasks()
{
try
{
await ThrowAfter(1000, "first");
await ThrowAfter(1000, "second");
Console.WriteLine("StartTwoTasks is Complate");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}

思考一下输出是什么?

答案是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static async void StartTwoTasksParallel()
{
try
{
Task t1 = ThrowAfter(1000, "first");
Console.WriteLine("t1 is Complate");
Task t2 = ThrowAfter(1000, "second");
Console.WriteLine("t2 is Complate");
await Task.WhenAll(t2, t1);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}

输出:

1
2
3
is Complate
t2 is Complate
second

从输出可以看出来,使用 WhenAll() 方法,两个任务都是执行完成的,但是,捕获异常只能捕获 WhenAll()方法参数中,排在最前面的,且第一个抛出异常的任务的消息,

上述方式有缺陷,只能抛出一个异常的任务的消息,可以将上面的方式再进化一下,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static async void StartTwoTasksParallelEx()
{
Task t1 = null;
Task t2 = null;
try
{
t1 = ThrowAfter(1000, "first");
t2 = ThrowAfter(1000, "second");
await Task.WhenAll(t2, t1);

}
catch (Exception ex)
{
if (t1.IsFaulted)
{
Console.WriteLine(t1.Exception.InnerException.Message);
}

if (t2.IsFaulted)
{
Console.WriteLine(t2.Exception.InnerException.Message);
}
}
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static async void StartTwoTasksParallelEx2()
{
Task t3 = null;
try
{
Task t1 = ThrowAfter(1000, "first");
Task t2 = ThrowAfter(1000, "second");
await (t3 = Task.WhenAll(t2, t1));

}
catch (Exception ex)
{
foreach (var item in t3.Exception.InnerExceptions)
{
Console.WriteLine("InnerException:" + item.Message);
}
}
}

输出: