为什么在任务中抛出的异常是静默异常,你永远不知道是否抛出了某个异常
try
{
Task task = new Task(
() => {
throw null;
}
);
task.Start();
}
catch
{
Console.WriteLine("Exception");
}
程序在完全沉默中成功运行! 线程的行为不同的地方
try
{
Thread thread = new Thread(
() => {
throw null;
}
);
thread .Start();
}
catch
{
Console.WriteLine("Exception");
}
在这种情况下将抛出空指针异常。 有什么区别?
答案 0 :(得分:15)
该场景的行为取决于您拥有的框架;在4.0中,你实际上需要小心 - 如果你不处理TaskScheduler.UnobservedTaskException
,它会在收集/完成后错误 ,并杀死你的进程强>
TaskScheduler.UnobservedTaskException += (sender, args) =>
{
Trace.WriteLine(args.Exception.Message); // somebody forgot to check!
args.SetObserved();
};
这改变了4.5,IIRC。
要检查可能失败的Task
的结果,您可以注册一个续集 - 即调用ContinueWith
并检查结果的异常。或者,访问任务的.Result
(也将执行隐式Wait()
)将重新显示发生的异常。观察任务的结果是很好的,因为它清除了终结标志,这意味着它可以更便宜地收集。
答案 1 :(得分:5)
不,任务不是线程。任务代表高级抽象 - 它们是本质上可并行化的工作单元。线程运行工作单元。
在您的第一个示例中,您创建了一个工作单元,然后告诉它自己运行(它是如何执行任务的实现细节)。而在您的第二个示例中,您明确地安排了一个工作单元(它将以与执行任务不同的方式出现)。
答案 2 :(得分:4)
以下假定使用.NET 4.0,并使用默认的TaskScheduler。
首先请注意,在您传递的代理中 内部引发了异常,因此它们是在不同的线程中引发的,而不是在您(逻辑上)正在执行catch
的线程中引发的。因此,异常必须从执行委托/ lambda代码的线程传播到启动线程/任务的线程。
请注意,对于Task
,我认为库也可以选择不在它自己的线程上运行它,而是在调用线程上运行(但我不确定这是否仅适用于{{ 1}}等,而不是“裸”Parallel.ForEach
对象。)
对于那两件事,必须完成两件事:
说完了,你的两个例子都没有等待线程/任务完成。在第一个示例中,您错过了Task
(或类似),而在第二个示例中错过了task.Wait()
。根据您的测试代码计时行为,这可能意味着您可能永远无法从线程/任务中观察到异常(上面的第1项)。
即使您添加了两个调用,这对我来说也是如此(同样是.NET 4.0):
任务示例:对thread.Join()
的调用实际上重新启动了最初在任务委托中未处理的异常(这是TPL将在内部为您做的事情),它确实将它包裹在task.Wait()
内,如果你使用的东西比平面的“全能”更精确,你可以看到它。
线程示例:委托引发的异常仍未处理,您的应用程序退出(除非您执行任何操作以处理unhandled exceptions differently)
换句话说,我会举例如下:
System.AggregateException
答案 3 :(得分:1)
我认为在Task
的情况下你没有得到异常,因为你没有在主线程中等待异常。你只是继续。放task.Wait()
,您将在主线程中获得异常。