如果没有等待异步任务抛出异常,它在哪里?

时间:2014-09-05 17:32:23

标签: c# .net task-parallel-library async-await .net-4.5

我有以下示例:(请同时阅读代码中的注释,因为它会更有意义)

public async Task<Task<Result>> MyAsyncMethod() 
{
    Task<Result> resultTask = await _mySender.PostAsync();
    return resultTask; 

    // in real-life case this returns to a different assembly which I can't change
   // but I need to do some exception handling on the Result in here
}

我们假设_ PostAsync的{​​{1}}方法如下所示:

mySender

问题是:

因为我没有等待public Task<Task<Result>> PostAsync() { Task<Result> result = GetSomeTask(); return result; } 中的实际Result以及if MyAsyncMethod方法抛出异常,在哪个上下文中会抛出并处理异常?< / p>

有什么方法可以处理我的程序集中的异常吗?

当我尝试将PostAsync更改为:

时,我感到很惊讶
MyAsyncMethod

此处捕获异常,如果没有等待实际结果的事件。碰巧 public async Task<Task<Result>> MyAsyncMethod() { try { Task<Result> resultTask = await _mySender.PostAsync(); return resultTask; } catch (MyCustomException ex) { } } 的结果已经可用,并且在此上下文中抛出异常对吗?

是否可以使用PostAsync来处理当前类中的异常?例如:

ContinueWith

3 个答案:

答案 0 :(得分:21)

这是很多问题要打包成一个问题&#34;但是好的......

  

如果没有等待,异步任务会抛出异常吗?

TaskScheduler.UnobservedTaskException事件引发了未观察到的任务异常。这个事件被提出&#34;最终&#34;因为在将异常视为未处理之前,任务实际上必须进行垃圾回收。

  

因为我没有等待MyAsyncMethod中的实际结果,并且如果PostAsync方法抛出异常,在哪个上下文中会抛出并处理异常?

使用async修饰符并返回Task的任何方法都会将所有异常放在返回的Task上。

  

有什么方法可以处理我的装配中的异常吗?

是的,您可以替换返回的任务,例如:

async Task<Result> HandleExceptionsAsync(Task<Result> original)
{
  try
  {
    return await original;
  }
  catch ...
}

public async Task<Task<Result>> MyAsyncMethod()
{
  Task<Result> resultTask = await _mySender.PostAsync();
  return HandleExceptionsAsync(resultTask);
}
  

当我尝试将MyAsyncMethod更改为[同步返回内部任务]时,我很惊讶这里发现了异常,即使没有等待实际结果。

这实际上意味着您调用的方法不是async Task,正如您的代码示例所示。它是一个非asyncTask - 返回方法,当其中一个方法抛出异常时,它就像任何其他异常一样被处理(即,它直接传递调用堆栈;它没有放在返回的Task)上。

  

是否可以使用ContinueWith来处理当前类中的异常?

是的,但await更清洁。

答案 1 :(得分:6)

我在Task上使用扩展方法进行通用错误处理。这提供了一种既可以记录所有错误又可以在发生错误时执行自定义操作的方法。

public static async void ErrorHandle(this Task task, Action action = null)
{
    try
    {
        await task.ConfigureAwait(false);
    }
    catch (Exception e)
    {
        Log.Error(e);
        if (action != null)
            action();
    }
}

我倾向于使用它时,我会“开火并忘记”Task

Task.Run(() => ProcessData(token), token).ErrorHandle(OnError);

答案 2 :(得分:4)

  

因为我没有等待MyAsyncMethod中的实际结果,并且如果PostAsync方法抛出异常,在哪个上下文中会抛出并处理异常?

如果您没有await代码中的任何任务或不指定延续,行为可能会因您使用的.NET框架版本而有所不同。在这两种情况下,返回的Task都会吞下异常,差异将在最终确定时发生:

  1. .NET 4.0 - 终结器线程将重新抛出吞下的异常。如果没有注册全局异常处理程序,它将终止进程

  2. .NET 4.5及更高版本 - 将吞下异常并且不会引起注意。

  3. 在这两种情况下,TaskScheduler.UnobservedTaskException事件都会触发:

      

    当出现故障的任务未被观察到的异常即将触发异常升级策略时,默认情况下会终止该进程。

    当非async任务返回方法同步执行时,异常会立即传播,这就是您在不使用await的情况下捕获异常的原因,但您绝对应该不< / strong>取决于代码中的内容。

      

    我有什么方法可以处理我的程序集中的异常

    是的,你可以。我会告诉你await你在装配中执行的任务。

    如果您没有等待任何事情,则没有理由使用async修饰符:

    public Task<Result> PostAsync() 
    {
         return GetSomeTask();
    }
    

    然后,您可以在awaitPostAsync并在那里捕获例外:

    public async Task<Result> MyAsyncMethod() 
    {
        try
        {
             // No need to use Task<Result> as await will unwrap the outter task
             return await _mySender.PostAsync();
         }
         catch (MyCustomException e)
         {
               // handle here
         }
    }
    

    您甚至可以进一步修改此代码并删除async关键字,并可能在调用堆栈{@ 1}}的方法中更高级地捕获异常。