任务和异常静默

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

以上是关于任务和异常静默的主要内容,如果未能解决你的问题,请参考以下文章

Oracle安装卸载任务

使用任务计划程序运行脚本,用户上下文完全静默,没有弹出窗口或 cmd flash

静默通知上的 iOS 后台发布请求

远程通知的后台任务在片​​刻后暂停

在 Task.WaitAll 中处理取消的任务和任务异常?

处理任务异常 - 自定义 TaskScheduler