现在模拟一个异步方法抛出了异常:
public static async Task ThrowAfter(int ms, string message)
{
await Task.Delay(ms);
throw new Exception(message);
}
思考一下, DontHandle()
方法是否能够捕获到异常?
public static void DontHandle()
{
try
{
ThrowAfter(1000, "first");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
答案是:不会捕获到异常!
因为 DontHandle()
方法在 ThrowAfter()
方法抛出异常之前,就已经执行完毕。
那么上述代码怎么才能捕获到异常呢?
若想要捕获异常则必须通过 await
关键字等待 ThrowAfter()
方法执行完成。
将上文中的代码段进行修改:
public static async void HandleoOnError()
{
try
{
await ThrowAfter(1000, "first");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
结果就会输出:
first
如果调用两个异步方法,每个都会抛出异常,该如何处理呢?
我们可以这样写:
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);
}
}
思考一下输出是什么?
答案是:
first
并没有预想中的两个异常都捕获打印出来,也没有看到“StartTwoTasks is Complate”这句话打印出来。因为使用 await
关键字之后,两次调用 ThrowAfter()
方法就变成了同步执行,捕获到第一次的异常之后直接进入到 catch
代码段,不再执行后续代码。
可以尝试解决这个问题,使用 Task.WhenAll()
方法,该方法不管任务是否抛出异常,都会等到两个任务完成。如下代码:
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);
}
}
输出:
t1 is Complate
t2 is Complate
second
从输出可以看出来,使用 WhenAll()
方法,两个任务都是执行完成的,但是,捕获异常只能捕获 WhenAll()
方法参数中,排在最前面的,且第一个抛出异常的任务的消息,
上述方式有缺陷,只能抛出一个异常的任务的消息,可以将上面的方式再进化一下,如下代码:
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);
}
}
}
输出:
first
second
在 try/catch
代码块外声明任务变量t1、t2,使他们可以在 try/catch
块内访问,在这里,使用了IsFaulted
属性,检查任务的状态,若IsFaulted
属性为 true
,则表示该任务出现异常,就可以使用 Task.Exception.InnerException
访问异常本身。
除了上述方式外,还有一种更好的获取所有任务的异常信息的方式,Task.WhenAll()
方法返回的结果其实也是一个 Task
对象,而 Task
有一个 Exception
属性,它的类型是 AggregateException
,是 Exception
的一个派生类,AggregateException
类有一个 InnerExceptions
属性(异常集合,包含 Task.WhenAll()
方法列表中所有异常任务的异常信息)。
有了这个属性则可以轻松遍历所有异常。如下代码:
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);
}
}
}
输出:
InnerException:second
InnerException:first
除了前面提到的异步方法异常处理的基本知识点,以下是一些进阶的异常处理技巧:
Task
或 ValueTask
对象中,并将其返回给调用方。这可以避免在异步操作中丢失异常信息。catch
块来捕获不同类型的异常,并根据需要执行不同的处理操作。还可以使用 finally
块来执行清理操作,例如释放资源或恢复状态。Task.WhenAll
方法来等待所有异步操作完成。如果任何一个异步操作失败,WhenAll
方法将返回一个 AggregateException
对象,其中包含所有失败的异常。Task.WhenAny
方法来等待第一个异步操作完成。如果第一个操作失败,WhenAny
方法将返回一个 AggregateException
对象,其中包含第一个失败的异常。ExceptionDispatchInfo
类。这个类可以捕获异常并将其存储在一个对象中,然后在需要时重新抛出异常。这可以帮助在异步操作中保留异常信息,并将其传递给调用方。总之,在异步方法中处理异常时,需要注意一些细节和技巧,例如正确处理异常、捕获多个异常、等待多个异步操作、以及使用 ExceptionDispatchInfo
类来捕获异常。掌握这些处理技巧可以帮助编写更可靠、更健壮的异步代码。
本文分享自 Niuery Diary 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!