使用TPL进行异常处理

时间:2013-02-13 12:50:54

标签: c# task-parallel-library

使用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
        });

更推荐使用哪种方法? 每个人的缺点和优点是什么?

5 个答案:

答案 0 :(得分:3)

这主要取决于您的设计需求。有些事情需要考虑:

抓住抛出它们的任务中的异常

  • 当任务代表一些不可分割的工作单元时,包括在特定异常类型之后进行清理。
  • 当特定异常类型由于某种原因不应在任务之外传播时,例如它需要包装在不同类型的外部异常中,以满足客户代码对合同的期望。

处理延续中的例外

  • 当异常清理应由不同的TaskScheduler安排时,例如在线程池上运行“主要”任务,但将所有异常记录编组到UI线程上。
  • 如果有多个延续是有意义的,每个都有异常,但这有点不寻常。
  • 要确保来自Task的您的代码未提供的异常被观察和处理,例如在TaskFactory.FromAsync创建的任务之后正确清理。虽然取决于具体情况,但也可以通过等待Task
  • 来完成

答案 1 :(得分:3)

如果你能够在方法中正确处理异常,那么任务本身就会抛弃你应该在第一个任务中捕获它,而不是在延续中,除非你有一些令人信服的理由不这样做。在与任务本身相同的范围内创建延续(如在第二个示例中所做的那样)是不必要地添加更多工作。

当从与定义任务的位置完全不同的范围处理异常时,处理延迟中的异常是有用的或必需的。例如,如果你有一个方法被赋予一些任意的任务,并且它不知道该任务的定义可能是什么,但它需要在代码抛出异常的情况下做一些你需要的事情有一个处理异常的延续。

请注意,如果 将具有处理异常的延续,则可以使用TaskContinuationOptions.OnlyOnFaulted仅在任务抛出异常时运行延续,而不是在内部执行检查继续的定义。

答案 2 :(得分:1)

在某种程度上,这是一个偏好问题,特别是如果你拥有'任务代码和调用代码。以下是一些需要考虑的事项。

首先,你应该only catch exceptions that you know how to handle。无论您是使用延续还是在操作中使用try / catch来处理它们,这都适用。

还请注意changed behaviour in .NET 4.5关于未捕获的异常。这种变化是有效的,从“纯粹主义”的方法(将未被捕获的任务例外的过程拆除)转变为不那么严厉的方法。尽管如此,依靠这种新行为并不是故意的。

至于你赞成的两种替代方案中,有选择第二种方法的论据:在延续中处理异常。在.NET中返回a的方法将越来越常见Task。例如,Stream.ReadAsync。要正确使用这些方法,您需要一个延续(使用传统方式,或使用带有新await功能的try / catch块,这相同,但更容易编码和读取)。因此,养成假设任何Task可能失败的习惯是好的,除非您明确知道其他情况,并编写适当的异常处理行为。

如果您感兴趣,可以使用以下方法在.NET 4.5中编写第二个示例。

async Task MyMethod()
{
    try
    {
        await Task.Run(
            () =>
            {
                // Some work.
            });
    }
    catch (SomeException ex)
    {
    }
}

最常见的另一个区别是 Windows窗体或WPF应用程序,其中您的代码是从UI线程调用的。这里使用await时TPL的默认行为是使用同步上下文运行continuation,该同步上下文将它们封送回UI线程。也就是说,如果从UI线程调用Task.Run,则继续也将在UI线程上运行。

如果要向用户显示一个对话框以响应异常,这将非常有用。您无法在Task内容中成功完成此操作。使用显式延续而非await时,您必须将使用TaskScheduler.FromCurrentSynchronizationContext创建的TaskScheduler传递给ContinueWith的相应重叠。

答案 3 :(得分:1)

你的两个例子在概念上是不同的。

第一个在执行任务内部处理您的异常。在catch之后运行的任何代码仍然会被执行。

第二个调度另一个异步任务,在第一个任务完成后,调度程序将始终运行该任务。

猜猜答案是否取决于你想要达到的目标 - 没有明确的答案 - 但第二个更符合tpl。

另外,在第二个例子中,task.IsFaulted更清楚task.Exception

答案 4 :(得分:-1)

我会说这取决于具体情况。正如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()我们没有的情况下,我们将它传播给调用者,声明我们处于不一致状态。