代码之家  ›  专栏  ›  技术社区  ›  MaYaN

为什么在使用async await时,会抛出unobservedtaskexception而忽略观察结果?

  •  4
  • MaYaN  · 技术社区  · 6 年前

    下面的场景在 .NET 4.5版 所以任何 UnobservedTaskException 没有 terminate the process 是的。

    我习惯于听任何 不可观察的异常 在我的应用程序开始时执行此命令引发:

    private void WatchForUnobservedTaskExceptions()
    {
      TaskScheduler.UnobservedTaskException += (sender, args) =>
      {
          args.Exception.Dump("Ooops");
      };
    }
    

    我还有一个助手方法 明确地 忽略我的任务引发的任何异常:

    public static Task IgnoreExceptions(Task task) 
      => task.ContinueWith(t =>
          {
              var ignored = t.Exception.Dump("Checked");
          },
          CancellationToken.None,
          TaskContinuationOptions.ExecuteSynchronously,
          TaskScheduler.Default);
    

    因此,如果我有以下代码执行:

    void Main()
    {
      WatchForUnobservedTaskExceptions();
    
      var task = Task.Factory.StartNew(() =>
      {
          Thread.Sleep(1000);
          throw new InvalidOperationException();
      });
    
      IgnoreExceptions(task);
    
      GC.Collect(2);
      GC.WaitForPendingFinalizers();
    
      Console.ReadLine();    
    }
    

    在我们从 Console.ReadLine() 我们什么也看不到 不可观察的异常 这是我们所期望的。

    但是如果我改变上面的 task 开始使用 async/await 一切都和以前一样:

    var task = Task.Factory.StartNew(async () =>
    {
        await Task.Delay(1000);
        throw new InvalidOperationException();
    });
    

    现在我们得到了 不可观察的异常 扔了调试代码时,会显示继续执行 t.Exception 存在 null 是的。

    在这两种情况下,如何正确地忽略异常?

    2 回复  |  直到 6 年前
        1
  •  4
  •   Peter Bons    6 年前

    要么使用

    var task = Task.Factory.StartNew(async () =>
    {
        await Task.Delay(1000);
        throw new InvalidOperationException();
    }).Unwrap();
    

    var task = Task.Run(async () =>
    {
        await Task.Delay(1000);
        throw new InvalidOperationException();
    });
    

    this blogpost about Task.Run vs Task.Factory.StartNew 关于将task.factory.startnew与异步修饰符一起使用

    通过在这里使用async关键字,编译器将把这个委托映射为 Func<Task<int>> :调用委托将返回 Task<int> 代表这个电话的最终完成既然代表是 函数<任务<int>> 我是说, TResult 任务<int> ,因此“t”的类型将是 Task<Task<int>> ,不是 任务<int> .

    为了处理这类情况,在.net 4中我们引入了展开方法。

    再来一点 background

    为什么不使用task.factory.startnew?

    .. 不理解异步委托。… . 问题是,当您将异步委托传递给startnew时,很自然会假设返回的任务表示该委托。但是,由于startnew不理解异步委托,因此该任务实际上表示的只是该委托的开始。这是编码器在异步代码中使用startnew时遇到的第一个陷阱之一。

    编辑

    类型 task 在里面 var task = Task.Factory.StartNew(async (...)) =>实际上 任务<任务<int>> 是的。你必须 Unwrap 为了得到源任务考虑到这一点:

    你只能打电话 展开 在一个 Task<Task>> 所以你可以在 IgnoreExceptions 为了适应这一点:

    void Main()
    {
        WatchForUnobservedTaskExceptions();
    
        var task = Task.Factory.StartNew(async () =>
        {
            await Task.Delay(1000);
            throw new InvalidOperationException();
        });
    
        IgnoreExceptions(task);
    
        GC.Collect(2);
        GC.WaitForPendingFinalizers();
    
        Console.ReadLine();
    }
    
    private void WatchForUnobservedTaskExceptions()
    {
        TaskScheduler.UnobservedTaskException += (sender, args) =>
        {
            args.Exception.Dump("Ooops");
        };
    }
    
    public static Task IgnoreExceptions(Task task)
      => task.ContinueWith(t =>
          {
              var ignored = t.Exception.Dump("Checked");
          },
          CancellationToken.None,
          TaskContinuationOptions.ExecuteSynchronously,
          TaskScheduler.Default);
    
    
    public static Task IgnoreExceptions(Task<Task> task)
    => task.Unwrap().ContinueWith(t =>
    {
        var ignored = t.Exception.Dump("Checked");
    },
    CancellationToken.None,
    TaskContinuationOptions.ExecuteSynchronously,
    TaskScheduler.Default);
    
        2
  •  2
  •   Euphoric    6 年前

    组合 var Task Task<T> 相互交织的关系掩盖了问题。如果我稍微重写一下代码,问题就显而易见了。

      Task<int> task1 = Task.Factory.StartNew(() =>
      {
         Thread.Sleep(1000);
         throw new InvalidOperationException();
         return 1;
      });
    
      Task<Task<int>> task2 = Task.Factory.StartNew(async () =>
      {
         await Task.Delay(1000);
         throw new InvalidOperationException();
         return 1;
      });
    

    这更能说明彼得·邦斯在说什么。