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

时间:2014-05-06 21:16:43

标签: 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?");
    }
}
}

输出:

I think everything is just peachy!

期望的输出:

I'm so lonely, won't anyone call me?
I'm so lonely, won't anyone call me?
I think everything is just peachy!

和/或简单地崩溃,即使这会比静默失败的异步任务有了巨大的改进!

编辑:根据此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>

3 个答案:

答案 0 :(得分:7)

来自评论:

  

我可以看到一切都有原因,但我不能否认   被这个有充分理由的想法让人大吃一惊   不应该通过设计发生。如果我不想等待任务怎么办?   我应该在这种情况下从不使用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);

根本不可能。 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);
}

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

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

我描述了async void方法in more details here的异常传播行为。

  好的,我得到了你所说的。按照设计,任务是永远不会的   与火灾和遗忘模式一起使用?只需将工作分发给   匿名线程还有其他方式吗?

我并不是说这些任务不适合用于“一劳永逸”的模式。我实际上认为他们非常适合这一点。有一些选项可以用任务来实现这个模式,我在上面显示了一个,另一个是Task.ContinueWith,或者你可以检查this question以获得更多的想法。

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

答案 1 :(得分:3)

您可以在app.config中设置ThrowUnobservedTaskExceptions以启用此行为(这是.NET 4中的行为)。

请注意,只有在问题Task被垃圾收集后才会引发异常,因此不会立即发生。这意味着您的测试可能仍然不一定打印出消息(除非您在此处明确调用GC.Collect进行测试)。

答案 2 :(得分:0)

好吧,这里有一些调用处理程序的代码(没有实现处理程序的事件参数),如果要激活并忘记某个操作并触发异常事件,则异步调用该任务,只需要添加事件参数。

任务无法在内部知道是否正在等待,所以我可以理解为什么会这样。它将带我做更多的工作和研究,为一个返回Task的方法实现一个事件处理程序,并且是等待的。如果我能看到这些库的源代码,那肯定会很好......

void FireAndForgetTaskWithExceptionHandling(Action a)
    {
        Task.Factory.StartNew(() =>
        {
            try
            {
                a.Invoke();
            }
            catch (Exception e)
            {
                Handler(new Object(), new EventArgs());
            }
        }
    );
    }

编辑:如果您要执行上述操作,您基本上只是调用操作并调用事件处理程序。

((Action)(<your lambda expression here>)).Invoke();