我见过的一个常见问题是管理任务中未处理的异常。它们不会导致崩溃,它们会无声地发生,我甚至无法在任务失败时触发事件!我已经看到用户开出自定义类和处理这个问题的东西,我的问题是,是否有一种“标准”的微软方式来处理这个问题?
为了举例,我制作了这个简单的控制台应用程序(.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>
答案 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();