任务和异常静默
Posted
技术标签:
【中文标题】任务和异常静默【英文标题】:Task and exception silence 【发布时间】:2012-02-06 20:30:54 【问题描述】:为什么任务中抛出的异常是静默异常,你永远不知道是否抛出了某个异常
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");
这种情况下会抛出空指针异常。 有什么区别?
【问题讨论】:
【参考方案1】:该场景的行为取决于您拥有的框架;在 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()
)将重新显示发生的异常。观察任务的结果是件好事,因为这会清除完成标志,这意味着可以更便宜地收集它。
【讨论】:
将版本号从 5.0 更改为 4.5 - 希望您不介意。 (它将是 C# 5,但 .NET 4.5。) TaskScheduler.UnobservedTaskException Event 的备注部分对 .NET 4.5 中发生的更改提供了一些有用的解释。【参考方案2】:不,任务不是线程。任务代表高级抽象——它们是本质上可并行化的工作单元。线程运行工作单元。
在您的第一个示例中,您创建了一个工作单元,然后告诉它自己运行(它如何运行是 Task 的实现细节)。而在您的第二个示例中,您明确安排了一个工作单元(它会以与 Task 的实现不同的方式出现)。
【讨论】:
OP 对为什么Task
不会抛出而 Thread
会更感兴趣。
@sleimanjneidi 任务将存储询问任务时的异常。在线程中没有固有的异常处理,它会向上传播直到异常被捕获。【参考方案3】:
以下假设为 .NET 4.0,并使用默认的 TaskScheduler。
首先请注意,异常是在您传递的委托内部引发的,因此它们是在不同的线程中引发的,而不是在您(逻辑上)正在执行catch
的线程中引发的。因此,必须以某种方式将异常从执行委托/lambda 代码的线程传播到启动线程/任务的线程。
请注意,对于Task
,我认为库也可以选择不在它自己的线程上运行它,而是在调用线程上运行它(但我不确定这是否仅适用于Parallel.ForEach
,等等,而不是“裸”Task
对象)。
为了这两个发生,必须满足两件事:
-
调用线程仍处于活动状态。否则就没有任何东西可以实际执行捕获。
必须以某种方式保留并重新引发线程/任务中引起的异常,以便您捕获它。
话虽如此,您的两个示例都不会等待线程/任务完成。在第一个示例中,您缺少 task.Wait()
(或类似名称),在第二个示例中缺少 thread.Join()
。根据您的测试代码计时行为,这可能意味着您可能永远无法从线程/任务中观察到异常(上面的第 1 项)。
即使你添加了这两个调用,这对我来说也是如此(同样是 .NET 4.0):
Task 示例:对task.Wait()
的调用实际上重新引发了最初在任务委托中未处理的异常(这是 TPL 将在内部为您执行的操作),它确实包装了它在System.AggregateException
内,您可以/将会看到您是否会使用比平面“包罗万象”更精确的东西。
线程示例:委托引发的异常仍未处理,您的应用程序将退出(除非您对unhandled exceptions differently进行任何处理)
换句话说,我将有如下示例:
// Thread example
var thread = new Thread(() => throw null; );
thread.Start();
thread.Join();
// Should never reach here, depending on timing sometimes not even
// until the Join() call. The process terminates as soon as the other
// thread runs the "throw null" code - which can logically happen somewhere
// after the "start" of the "Start()" call.
// Task example
try
var task = new Task(() => throw null; );
task.Start();
task.Wait();
catch (AggregateException ex)
Console.WriteLine("Exception: " + ex);
【讨论】:
【参考方案4】:我认为在Task
的情况下你没有得到异常,因为你没有在主线程中等待异常。你只是在继续。输入task.Wait()
,你会在主线程中得到异常。
【讨论】:
非常正确,但公平地说,在大多数情况下,我们不会立即致电Wait
。以上是关于任务和异常静默的主要内容,如果未能解决你的问题,请参考以下文章