C# · 12月 26, 2021

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

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

为了举例,我制作了这个简单的控制台应用程序(.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>解决方法 来自评论:

I can see that there is a reason for everything,but I can’t deny
being flabbergasted by the idea that there’s a good reason that this
should not happen by design. What if I don’t want to await a Task,
should I just never use the Task class in that case? What’s the
drawback to this having an event that can be subscribed to? I’m going
to write an extension method,and by all means,tell me why it
shouldn’t be this way

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

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

… there’s a good reason that should not happen by design

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

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之后的代码没有机会以其方式处理异常.因此,目前的行为经过深思熟虑并且非常有意义.

如果您仍希望在发生即发即弃任务时立即观察异常,请使用辅助异步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();

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

OK,I get what you’re saying. By design,are tasks meant to be never
used with a fire and forget pattern? Just dispatch the work to an
anonymous thread some other way?

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

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