TPL的异常处理
本文关键字:异常处理 TPL | 更新日期: 2023-09-27 17:58:53
使用TPL/Tasks,我可以使用内部try/catch语句执行异常处理:
Task.Factory.StartNew(
()=>
{
try
{
// Do stuff
}
catch
{
// Handle exception
}
});
或者使用ContinueWith,就像这样:
Task.Factory.StartNew(
()=>
{
// Do stuff
}).ContinueWith(
task =>
{
if(task.Exception != null)
// Handle exception
});
更建议使用哪种方法?每种方法的利弊是什么?
如果你能够在任务本身抛出的方法中正确处理异常,你应该在第一个任务中捕获它,而不是在延续中,除非你有一些令人信服的理由不这样做。在与任务本身相同的范围内创建延续(就像第二个例子中所做的那样)会不必要地增加更多的工作。
当从与定义任务的范围完全不同的范围处理异常时,在延续中处理异常既有用又必要。例如,如果您有一个方法被赋予了某个任意任务,并且它不知道该任务的定义可能是什么,但它需要在代码引发异常的情况下做一些事情,那么您需要有一个处理异常的延续。
请注意,如果将有一个处理异常的延续,则可以使用TaskContinuationOptions.OnlyOnFaulted
仅在任务引发异常时运行延续,而不是在延续的定义中进行检查。
这在很大程度上取决于您的设计需求。需要考虑的一些事项:
在抛出异常的任务中捕获异常
- 当一个任务表示某个不可分割的工作单元,包括在特定异常类型之后进行清理时
- 当特定的异常类型由于某种原因不应该在任务之外传播时,例如,它需要封装在不同类型的外部异常中,以满足客户端代码对契约的期望
在延续中处理异常
- 当异常清理应由不同的
TaskScheduler
调度时,例如在线程池上运行"主要"任务,但将所有异常日志整理到UI线程上 - 如果有意义的话,让多个continuation每个都做不同的事情,但有一个例外,尽管这有点不寻常
- 为了确保未提供代码的
Task
的异常得到适当的观察和处理,例如在TaskFactory.FromAsync
创建的任务之后进行适当的清理。尽管这取决于具体情况,但也可以通过等待Task
来完成
您的两个示例在概念上不同。
第一个在执行任务内部处理异常。catch之后运行的任何代码仍将被执行。
第二个调度另一个异步任务,该任务将始终由调度程序在第一个任务完成后运行。
我猜答案是,这取决于你想要实现的目标——没有明确的答案——但第二个答案更符合tpl。
此外,在第二个示例中,task。IsFaulted更清楚这项任务。异常
在某种程度上,这是一个偏好问题,尤其是如果您"拥有"任务代码和调用代码。以下是一些需要考虑的事项。
首先,您应该只捕获您知道如何处理的异常。无论您是用continuation还是用操作中的try/catch来处理它们,这都适用。
还请注意中更改的行为。NET 4.5关于未捕获的异常。这一变化从"纯粹主义"方法(在未捕获的任务异常上拆除流程)有效地转变为不那么严厉的方法。不过,故意依赖这种新行为是不好的。
至于你的两个选择中的哪一个,有一个选择第二个的论点:在延续中处理异常它将在中越来越常见。NET的方法来返回Task
。例如,Stream。ReadAsync。要正确使用这些方法,您需要一个延续(可以是传统的方式,也可以使用具有新await
功能的try/catch块,这相当于相同的东西,但更容易编码和读取)。因此,养成这样一个习惯,即假设任何Task
都可能失败,除非您明确知道其他情况,并编码适当的异常处理行为。
如果您感兴趣,这里有一种在中对第二个示例进行编码的替代方法。净4.5。
async Task MyMethod()
{
try
{
await Task.Run(
() =>
{
// Some work.
});
}
catch (SomeException ex)
{
}
}
另一个差异最常见于Windows窗体或WPF应用程序,其中代码是从UI线程调用的。这里,当使用await
时,TPL的默认行为是使用同步上下文运行延续,该同步上下文将它们封送回UI线程。也就是说,如果从UI线程调用Task.Run
,那么continuation也将在UI线程上运行。
如果您希望向用户显示对话框以响应异常,这将非常有用。在Task
工作负载中,您将无法成功地做到这一点。当使用显式continuations而不是await
时,必须传递使用TaskScheduler创建的TaskScheduler
。从CurrentSynchronizationContext到ContinueWith的适当重载。
我认为这取决于上下文。正如Olly所说,您应该只处理您知道如何处理的异常。我想说如果你知道如何处理异常,你应该处理它。
一个例子是,如果您有一个任务应该从文件中加载一些数据,或者回退到某个默认值(这可能引发异常),那么一种方法是(伪代码):
Task.Factory.StartNew(()=>
{
MyObject objectToSet = null;
try
{
objectToSet = File.Open("mydata");
}
catch (FileException ex)
{
// this will catch the FileException because we know how to handle that!
// the following will however throw an exception that we cannot handle
objectToSet = GetFallBackValue();
}
// when we are here we promise that the objectToSet is valid.
});
在File.Open
的情况下,我们知道如何继续。在GetFallBackValue()
的情况下,我们没有,所以我们将其传播给调用者,声明我们处于不一致的状态。