处理任务异常 - 自定义TaskScheduler

时间:2016-03-12 01:40:38

标签: c# multithreading exception-handling .net-4.0 task-parallel-library

我正在编写一个自定义FIFO排队的线程限制任务调度程序。

  1. 我希望能够保证未处理的任务异常立即冒泡或升级,然后继续结束此过程。

  2. 我还希望Task.Wait()处理可能会弹出AggregateException,而不会导致流程结束。

  3. 可以同时满足这两个要求吗?我是否以错误的方式思考这个问题?

    更多信息

    问题的关键在于我并不总是希望Task.Wait()完成任务。

    在我的自定义任务调度程序上执行任务后,我知道未处理的任务异常最终会escalate when the Task is cleaned up by the GC

      

    如果您没有等待传播异常的任务或访问其Exception属性,则在对任务进行垃圾回收时,将根据.NET异常策略升级异常。

    但谁知道何时会在应用程序执行的环境中发生 - 这在设计时无法确定。

    对我而言,这意味着任务中未处理的异常可能无法检测到 - 永远。这会让我的口中感觉不好。

    如果我想确保立即升级未处理的任务异常,我可以在执行任务后在任务计划程序中执行以下操作:

    while ( taskOnQueue )
    {
        /// dequeue a task
    
        TryExecuteTask(task);
    
        if (task.IsFaulted) 
        { 
            throw task.Exception.Flatten(); 
        }
    }
    

    但通过这样做,我基本上保证异常将始终终止该过程,无论在AggregateException(或Task.Wait()事件中捕获TaskScheduler.UnobservedException如何处理它处理程序)。

    鉴于这些选择 - 我真的必须选择其中一种吗?

3 个答案:

答案 0 :(得分:1)

  

鉴于这些选择 - 我真的必须选择其中一种吗?

是的,你差不多了。确定Task不会Wait的唯一方法是找出执行程序无法访问Task,这就是GC的作用。即使您可以确定某个线程当前Wait是否Task,这还不够:Task可能存储在某个数据结构中,它将是{{1}在未来的某个时刻开始。

我能想到的唯一选择,这是一个非常糟糕的选择,特别是从性能角度来看,是在Wait完成后调用GC.Collect()。这样,在完成时无法访问的每个Task将立即被视为未处理。但即使这样也不可靠,因为Task在完成后可能无法访问。

答案 1 :(得分:1)

  

对我而言,这意味着任务中未处理的异常可能永远不会被发现。这让我的口味不好。

这是真的,你无法改变它。第(1)点是失败的原因。

我不明白以下几点:

if (task.IsFaulted) 
{ 
    throw task.Exception.Flatten(); 
}

这意味着任何任务异常,即使是已处理的异常,也会抛出此异常。这限制了您可以在此调度程序上合理创建的任务。

(另外,我不明白你为什么要变平;甚至task.GetAwaiter().GetResult()更好。也许你应该换行而不是虚假的重新投掷。)

另见svicks回答。

您是否只是挂钩未处理的异常事件并将此类情况报告给开发人员?未处理的任务异常有时是错误,但根据我的经验很少致命。

如果你真的不需要任务来抛出异常,你可以使用包装任务主体的辅助方法来安排任务:

try {
 body();
} catch (...) { ... }

这将是一种相当干净的方式,灵活且不依赖于自定义调度程序。

答案 2 :(得分:0)

发布回答以便我可以描述对 usr 的评论中讨论的选项非常重要的详细信息。

当使用单独的方法记录或处理任务异常时,有(至少)两种基于任务的方法:

  1. 使用task.ContinueWith()
  2. 的续作任务
  3. 在第一个
  4. 之后立即创建并安排的第二个任务

    在我的情况下,区别很重要,因为自定义调度程序负责保证在保证数量的线程中呈现给它的任务的FIFO调度。

    注意:由于自定义调度程序的操作,第二个任务保证在第一个任务完成后与第一个任务运行在同一个线程上。

    选项1 - 延续任务

    Task task = new Task(() => DoStuff());
    task.ContinueWith(t => HandleTaskExceptions(t), customScheduler);
    task.Start(customScheduler);
    

    选项2 - 在第一个

    之后立即安排的第二个任务
    Task task = new Task(() => DoStuff());
    task.Start(customScheduler);
    
    Task task2 = new Task(() => HandleTaskExceptions(task));
    task2.Start(customScheduler);
    

    区别在于第二个任务计划运行的时间。

    将选项1与task continuation means that

    一起使用
      

    在当前任务完成之前,返回的任务不会被安排执行,是否由于成功运行而完成,由于未处理的异常而导致错误,或者由于被取消而提前退出。

    这意味着可以将延续放置在任务队列的末尾,这进一步意味着在完成其他工作之后,可能无法处理或记录异常。这可能会影响整个申请状态。

    使用选项2,保证在完成第一个任务后立即处理异常,因为handler-task是队列中的下一个。

    HandleTaskExceptions()可以简单如下:

    void HandleTaskExceptions(Task task)
    {
        if (task.IsFaulted)
        {
            /// handle task exceptions
        }
    }