【问题标题】:Where does an async Task throw Exception if it is not awaited?如果不等待异步任务,它会在哪里抛出异常?
【发布时间】:2014-10-30 17:11:29
【问题描述】:

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

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
}

假设 _mySenderPostAsync 方法如下所示:

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

问题是:

由于我没有在MyAsyncMethod 中等待实际的Result,如果PostAsync 方法抛出异常,那么将在哪个上下文中抛出和处理异常?

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

当我尝试将MyAsyncMethod 更改为:

 public async Task<Task<Result>> MyAsyncMethod() 
{
  try 
  {
    Task<Result> resultTask = await _mySender.PostAsync();
    return resultTask; 
  }
  catch (MyCustomException ex) 
  {
  } 
}

这里捕获了异常,如果没有等待实际结果的事件。碰巧PostAsync 的结果已经可用,并且在此上下文中抛出异常对吗?

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

public async Task<Task<Result>> MyAsyncMethod() 
{
    Task<Result> resultTask = await _mySender.PostAsync();
    var exceptionHandlingTask = resultTask.ContinueWith(t => { handle(t.Exception)}, TaskContinuationOptions.OnlyOnFaulted);
    return resultTask;
}

【问题讨论】:

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


【解决方案1】:

我使用扩展方法对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);

【讨论】:

  • 这不是回答所提出的问题。此外,使用 TPL 的主要原因之一是其出色的错误处理功能。为什么你会不遗余力地回到错误处理的回调风格呢?
  • 我可以看到使用它来进行射击和忘记动作。例如,当我执行一堆可以并行运行的任务时,我不想等待每个任务,但仍希望尽快抛出异常(即用于日志记录)。
【解决方案2】:

这是很多问题要打包成一个“问题”,但是好的...

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

TaskScheduler.UnobservedTaskException 事件引发未观察到的任务异常。这个事件是“最终”引发的,因为在将其异常视为未处理之前,该任务实际上必须被垃圾回收。

由于我不等待 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-returning 方法,当其中一个方法抛出异常时,它会像任何其他异常一样被处理(即,它直接向上传递调用堆栈;它不会放在返回的 @ 987654329@).

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

是的,但await 更干净。

【讨论】:

  • 嗨,斯蒂芬,感谢您的回答。你是对的例子是不正确的,该方法是一个非异步返回任务。去试试你的建议谢谢
  • 嘿@Dan Dinu,我知道已经有一段时间了,但你为什么不接受这么好的答案?
【解决方案3】:

由于我不等待 MyAsyncMethod 中的实际结果,并且如果 PostAsync 方法抛出异常,那么将在哪个上下文中抛出和处理异常?

如果您没有 await 代码中的任何任务或未分配延续,则行为可能会因您使用的 .NET 框架版本而异。在这两种情况下,返回的Task 都会吞下异常,差异会在finalization 出现:

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

  2. .NET 4.5 及更高版本 - 异常会被吞没,不会被注意到。

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

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

当一个非 async 任务返回方法同步执行时,异常会立即传播,这就是为什么你在不使用 await 的情况下捕获异常,但你绝对应该依赖在你的代码中。

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

是的,你可以。对于您在程序集中执行的任务,我会建议您 await

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

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

然后,您可以在PostAsyncawait 并在那里捕获异常:

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 关键字,并可能将异常捕获到调用堆栈更高的调用 MyAsyncMethod 的方法。

【讨论】:

  • 如果在async 方法中抛出异常,即使在第一个await 之前,它仍然会被包裹在返回的Task 中。它不会导致实际的方法调用抛出异常,即使它在同步运行时遇到了该异常。
  • 对,这是非异步Task返回方法。我修改了。
猜你喜欢
  • 2018-07-07
  • 1970-01-01
  • 1970-01-01
  • 2014-08-17
  • 2017-06-08
  • 2019-04-26
  • 2014-03-18
  • 2014-09-06
  • 2019-11-14
相关资源
最近更新 更多