system.threading.task - 为什么不发生TaskScheduler.UnobservedTaskException事件?我能解决这个问题吗?

Joh*_*ohn 7 c# task-parallel-library

我见过的一个常见问题是管理任务中未处理的异常.它们不会导致崩溃,它们会无声地发生,我甚至无法在任务失败时触发事件!我已经看到用户开出自定义类和处理这个的东西,我的问题是,是否有一种"标准"的微软方式来处理这个问题?

为了举例,我制作了这个简单的控制台应用程序(.NET 4.5.1)来演示这个问题.是否可以修改这些任务以便可以异步执行这些任务,但在遇到未处理的异常时调用"handler"?或者至少崩溃?我认为这就是UnobservedTaskException应该做的事情.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication4
{
class Program
{
    static void Main(string[] args)
    {
        TaskScheduler.UnobservedTaskException += Handler;
        AppDomain.CurrentDomain.UnhandledException += Handler;
        Task.Run(() => { throw new ApplicationException("I'll throw an unhandled exception"); });
        Task.Factory.StartNew(() => { throw new ApplicationException("I'll throw an unhandled exception too"); });
        System.Threading.Thread.Sleep(2000);
        Console.WriteLine("I think everything is just peachy!");
        System.Threading.Thread.Sleep(10000);
    }
    private static void Handler(Object sender, EventArgs e)
    {
        Console.WriteLine("I'm so lonely, won't anyone call me?");
    }
}
}
Run Code Online (Sandbox Code Playgroud)

输出:

I think everything is just peachy!
Run Code Online (Sandbox Code Playgroud)

期望的输出:

I'm so lonely, won't anyone call me?
I'm so lonely, won't anyone call me?
I think everything is just peachy!
Run Code Online (Sandbox Code Playgroud)

和/或简单地崩溃,即使这将是对异步任务失败的巨大改进!

编辑:根据此MSDN文章http://msdn.microsoft.com/en-us/library/jj160346%28v=vs.110%29.aspx将此添加到app.config ,但没有更改:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <runtime>
    <ThrowUnobservedTaskExceptions enabled="true"/>
  </runtime>
</configuration>
Run Code Online (Sandbox Code Playgroud)

nos*_*tio 7

来自评论:

我可以看到所有事情都有原因,但我不能否认被认为有充分理由不应该通过设计实现这一点的想法.如果我不想等待一个Task怎么办?在这种情况下我应该永远不使用Task类吗?有可订阅的活动有什么缺点?我将编写一个扩展方法,并且无论如何,请告诉我为什么不应该这样

Task对象的本质是它将来会被完成.预计任务的结果或异常将在未来被观察到,很可能在不同的堆栈帧上或甚至在不同的线程上.这就是为什么Framework(或编译器生成的代码,用于async Task方法)将异常存储在任务中并且不立即抛出它.

您不会在此处观察任务的结果,甚至不会在任何地方存储对任务对象的引用.从本质上讲,你正在做一个即发即弃的电话.编译器警告(告知不要等待任务),但它并没有消除托管Task对象仍然被创建的事实,并且在它被垃圾收集之前一直闲逛.届时,TaskScheduler.UnobservedTaskException将触发事件.

......有一个很好的理由不应该在设计上发生

如果上面的方法是一个糟糕的设计,那么什么是好的?如果立即抛出异常,以下可能如何工作:

var task = Task.Run(() => {
    throw new ApplicationException("I'll throw an unhanded exception"); });
Thread.Sleep(1000);
if (task.IsFaulted)
  Console.WriteLine(task.Exception.InnerException.Message);
Run Code Online (Sandbox Code Playgroud)

它根本不可能.之后的代码Sleep没有机会以其方式处理异常.因此,目前的行为经过深思熟虑并且非常有意义.

如果您仍希望在发生即发即弃任务时立即观察异常,请使用辅助async void方法:

public static class TaskExt
{
    public static async void Observe(
        this Task @this,
        bool continueOnCapturedContext = true)
    {
        await @this.ConfigureAwait(continueOnCapturedContext);
    } 
}

static void Main(string[] args)
{
    TaskScheduler.UnobservedTaskException += Handler;
    AppDomain.CurrentDomain.UnhandledException += Handler;

    Task.Run(() => { throw new ApplicationException("I'll throw an unhanded exception"); })
        .Observe();

    Task.Factory.StartNew(() => { throw new ApplicationException("I'll throw an unhanded exception too"); })
        .Observe();

    System.Threading.Thread.Sleep(2000);
    Console.WriteLine("I think everything is just peachy!");
    System.Threading.Thread.Sleep(10000);
}
Run Code Online (Sandbox Code Playgroud)

您还可以使用async voidlambda进行即发即弃电话:

Action fireAndForget = async () => 
{ 
    try 
    {
        await Task.Run(...); 
    }
    catch(e) { /* handle errors */ };
};
fireAndForget();
Run Code Online (Sandbox Code Playgroud)

在这里更详细地描述了async void方法的异常传播行为.

好的,我得到了你所说的.按照设计,任务是不是永远不会用火和遗忘模式?只是以其他方式将工作发送到匿名线程?

我并不是说这些任务并不意味着可以使用"一劳永逸"的模式.我实际上认为他们非常适合这一点.有一些选项可以用任务来实现这个模式,我在上面展示了一个,另一个是Task.ContinueWith,或者你可以检查这个问题以获得更多的想法.

一个不同的问题是,我几乎能想象一个有用的场景,我也不会要观察任务的完成状态,即使是发射后不管叫像上面.这样的任务会做什么工作?即使是日志记录,我仍然希望确保成功编写日志文件.此外,如果父进程在任务完成之前结束,该怎么办?根据我的经验,我总是使用类似的东西QueueAsync来记录被解雇的任务.