【问题标题】:Why can't I catch an exception from async code?为什么我不能从异步代码中捕获异常?
【发布时间】:2013-11-08 17:55:53
【问题描述】:

我读到的每一个地方都说下面的代码应该可以工作,但事实并非如此。

public async Task DoSomething(int x)
{
   try
   {
      // Asynchronous implementation.
      await Task.Run(() => {
      throw new Exception();
      x++;
      });
   }
   catch (Exception ex)
   {
      // Handle exceptions ?
   }
}

也就是说,我没有捕捉到任何东西,并得到一个源自“抛出”行的“未处理异常”。我在这里一无所知。

【问题讨论】:

  • 因为它是在另一个线程的上下文中运行的。
  • 你在哪里读到这会起作用?
  • 这对我有用。您可能会在调试器中看到已处理的异常。
  • @JonSkeet Unreachable code 是唯一的警告,而不是错误。
  • @ScottChamberlain:Doh,你是对的。糟糕 :) (这是 Java 中的一个错误……我只能为自己辩护,虽然它很弱,但它显然只是作为警告出现的,我只是没有正确阅读它!)

标签: c# .net c#-5.0


【解决方案1】:

您已启用“仅我的代码”选项。有了这个,它正在考虑关于“只是你的代码”的未处理异常——因为其他代码正在捕获异常并将其填充到任务中,稍后将在 await 调用中重新抛出并被你的 catch 捕获陈述。

如果不附加到调试器中,您的 catch 语句将被触发,它会按您的预期运行。或者您可以从调试器中继续,它会按预期运行。

最好的办法是关闭“仅我的代码”。 IMO,它造成的混乱比它的价值更多。

【讨论】:

  • 嘿,一个真正的答案(在一群人中“它对我有用”,好像他们认为这对任何人都有帮助)。如今,VS 中确实有很多选项会导致巨大的、令人困惑的问题(“启用本机代码调试”是另一个问题)。
  • 这帮助我解决了一个相关问题,即调试器在等待时捕获异常,而不是在发生我真正问题的调用堆栈更深处。
【解决方案2】:

正如 SLaks 所说,您的代码运行良好。

我强烈怀疑你过度简化了你的例子,并且在你的代码中有一个async void

以下工作正常

private static void Main(string[] args)
{
    CallAsync();
    Console.Read();
}

public static async void CallAsync()
{
    try
    {
        await DoSomething();
    }
    catch (Exception)
    {
        // Handle exceptions ?
        Console.WriteLine("In the catch");
    }
}

public static Task DoSomething()
{
    return Task.Run(() =>
    {
        throw new Exception();
    });
}

以下不起作用

private static void Main(string[] args)
{
    CallAsync();
    Console.Read();
}

public static void CallAsync()
{
    try
    {
        DoSomething();
    }
    catch (Exception)
    {
        // Handle exceptions ?
        Console.WriteLine("In the catch");
    }
}

public static async void DoSomething()
{
    await Task.Run(() =>
    {
        throw new Exception();
    });
}

http://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Async void 方法具有不同的错误处理语义。当一个 异常是从异步任务或异步任务方法中抛出的,即 异常被捕获并放置在 Task 对象上。使用异步无效 方法,没有 Task 对象,所以任何异常都会抛出 async void 方法将直接在 在异步 void 方法时处于活动状态的 SynchronizationContext 开始了。图 2 说明了从 async void 引发的异常 方法不能自然捕捉。

【讨论】:

    【解决方案3】:

    您的代码目前甚至无法完全编译,因为x++; 语句无法访问。始终注意警告。

    但是,修复后,它工作正常:

    using System;
    using System.Threading.Tasks;
    
    class Test
    {
        static void Main(string[] args)
        {
            DoSomething(10).Wait();
        }
    
        public static async Task DoSomething(int x)
        {
            try
            {
                // Asynchronous implementation.
                await Task.Run(() => {
                    throw new Exception("Bang!");
                });
            }
            catch (Exception ex)
            {
                Console.WriteLine("I caught an exception! {0}", ex.Message);
            }
        }
    }
    

    输出:

    I caught an exception! Bang!
    

    (请注意,如果您在 WinForms 应用程序中尝试上述代码,您将遇到死锁,因为您将等待需要返回 UI 线程的任务。我们在控制台应用程序中没问题因为任务将在线程池线程上恢复。)

    我怀疑问题实际上只是调试问题 - 调试器可能认为它未处理,即使它已处理。

    【讨论】:

      【解决方案4】:

      不要使用await,而是访问Task.Result 属性并在该访问权限周围放置trycatch。您也可以按照here 的示例尝试该样式。

      请记住,在任务线程上下文中引发的所有异常都包含在 AggregateException 中。

      【讨论】:

      • 否; await 在这里工作正常。你永远不应该使用Task.Result;这是一个容易死锁的同步调用。
      • @SLaks await 实际上只捕获线程 ID 并在由底层线程池线程分配的 Task.Result 上设置一个信号。一旦结果被分配(返回),它有一些逻辑来决定它是否需要形成一个延续或将结果返回到原始线程的堆栈。访问Task.Result 是同步的,但它本身并没有“死锁”,它会阻塞。此外,关键是要以任何可能的方式为他们工作,不一定是完美的方式。这只是一个解决方案,而不是最佳实践。
      • @MichaelJ.Gray 是的,但是如果捕获了同步上下文并且 async 方法实际上执行了异步操作,那么您在等待延续运行时会阻止延续运行,从而导致僵局。这在任何异步方法中都非常容易做到。除了首先破坏了使用async 方法的全部目的。如果您要进行阻塞等待,您也可以使方法同步,以免对调用者撒谎。此外,await 将解包并重新抛出等待操作的任何异常。
      • @Servy 所以你的意思是如果你读到Task.Result 并且父任务方法调用另一个async 方法并等待值,它会导致死锁吗?请记住,await 会阻止或创建延续。很多时候我发现它甚至不是异步的,只是立即返回(同步,甚至),特别是如果你有快速的资源访问或者在很少或没有负载的情况下进行 CPU 密集型工作。
      • @MichaelJ.Gray 不,我的意思是,如果您在任务上调用 Result,并且该任务在内部调用 await,那么您的代码将死锁,因为您正在等待该任务on 有一个无法运行的计划延续,因为您正在阻止当前的同步上下文。这就像等待 101 ......有一百万篇关于它的文章。
      【解决方案5】:

      例外是不 cuaght。 原因是 - 当执行下面的语句时

      await Task.Run(() => {
                  throw new Exception("Bang!");
              });
      

      它在一个单独的线程上。该线程上引发的异常未被捕获。

      如下图所示

      await Task.Run(() => {
      
                      try
                      {
                          throw new Exception("Bang!");
                      }
                      catch (Exception ex)
                      {
                          Console.WriteLine(ex.Message);   
                      }
      
              });
      

      【讨论】:

      • 你是对的,在另一个线程中引发了异常,并且该线程捕获了异常,向运行延续的线程指示另一个线程抛出了异常,并导致异常被重新- 在执行延续的线程中抛出。
      猜你喜欢
      • 2021-03-16
      • 1970-01-01
      • 2014-11-24
      • 1970-01-01
      • 1970-01-01
      • 2010-12-07
      • 2019-10-10
      • 2015-04-26
      • 2012-12-06
      相关资源
      最近更新 更多