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)
来自评论:
我可以看到所有事情都有原因,但我不能否认被认为有充分理由不应该通过设计实现这一点的想法.如果我不想等待一个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来记录被解雇的任务.