C#中的Task <T>和TaskContinuationOptions澄清?

Roy*_*mir 8 .net c# multithreading .net-4.0 task-parallel-library

我有这个简单的代码:

var g=  Task.Factory.StartNew<int> (() => 8)
       .ContinueWith (ant =>{throw null;})
       .ContinueWith (a =>{ Console.WriteLine("OK");},TaskContinuationOptions.NotOnFaulted);

 try{
      Console.WriteLine("1");
      g.Wait();
      Console.WriteLine("2");
     }

catch (AggregateException  ex)
      {Console.WriteLine("catch"); }
Run Code Online (Sandbox Code Playgroud)

输出 :

1
catch
System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread.
Run Code Online (Sandbox Code Playgroud)

msdn:

TaskContinuationOptions.NotOnFaulted

指定如果延续任务的前端抛出未处理的异常,则不应该对其进行调度.此选项对多任务延续无效.

好 .

在此输入图像描述

并且没关系 - 不显示此行导致prev行DID抛出异常.

问题:

  • 我是否AggregateException因为没有检查过Exception房产而得到例外?

  • 我是否必须始终检查先前是否会抛出异常(在每一行?)?(我不能检查每一行!它没有任何意义,非常烦人)

  • 是不是该try catch块应该吞下这个例外?(我认为所有异常都会导致等待方法....所以?)

Ser*_*kov 5

我是否AggregateException因为没有检查过Exception房产而得到例外?

不,你得到一个例外,因为任务g由TPL取消(因为,如msdn所述,如果antescendent任务抛出异常,则不会安排此任务).

我们这里有3个任务:

  1. 原始任务(使用StartNew)
  2. 第一个继续任务(抛出异常)
  3. 第二个继续任务(打印正常)(这是您的代码中的g任务).

问题是,只有当第二个任务成功完成时,才会要求TPL启动3d任务.这意味着如果不满足此条件,TPL将完全取消您新创建的任务.

你有未被观察到的任务异常,因为你有一个你从未观察过的临时任务(我的列表中的任务2).因为你从来没有观察过它的故障状态,它会抛出终结器来告诉你它.

您可以通过在catch块中打印任务的状态来检查:

catch (AggregateException ex)
{ 
    Console.WriteLine("catch");
    // Will print: Status in catch: Canceled
    Console.WriteLine("Status in catch: {0}", g.Status);
}
Run Code Online (Sandbox Code Playgroud)

我是否必须始终检查先前是否会抛出异常(在每一行?)?(我不能检查每一行!它没有任何意义,非常烦人)

是的,您应该观察先前的任务异常以避免此问题:

static class TaskEx
{
    public static Task ObserverExceptions(this Task task)
    {
        task.ContinueWith(t => { var ignore = t.Exception; },
                            TaskContinuationOptions.OnlyOnFaulted);
        return task;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后使用它如下:

var g=  Task.Factory.StartNew<int> (() => 8)
       .ContinueWith (ant =>{throw null;})
       .ObserveExceptions()
       .ContinueWith (a =>{ Console.WriteLine("OK");});

 try{
      Console.WriteLine("1");
      g.Wait();
      Console.WriteLine("2");
     }

catch (AggregateException  ex)
      {Console.WriteLine("catch"); }
Run Code Online (Sandbox Code Playgroud)

更新:为最后一颗子弹添加了解决方案

是不是try catch块应该吞下异常?(我认为所有异常都会导致等待方法....所以?)

我们的项目中有一组扩展方法(称为TransformWith),它可以解决这个特定问题,并获得以下内容:

  1. 异常会冒泡到catch块和
  2. 我们不会崩溃应用程序 TaskUnobservedException

这里的用法

var g = Task.Factory.StartNew(() => 8)
       .ContinueWith(ant => { throw null; })
       // Using our extension method instead of simple ContinueWith
       .TransformWith(t => Console.WriteLine("OK"));

try
{
    Console.WriteLine("1");
    // Will fail with NullReferenceException (inside AggregateExcpetion)
    g.Wait();
    Console.WriteLine("2");
}

catch (AggregateException ex)
{
    // ex.InnerException is a NullReferenceException
    Console.WriteLine(ex.InnerException);
}
Run Code Online (Sandbox Code Playgroud)

这是一个扩展方法:

static class TaskEx
{
    public static Task TransformWith(this Task future, Action<Task> continuation)
    {
        var tcs = new TaskCompletionSource<object>();
        future
            .ContinueWith(t =>
            {
                if (t.IsCanceled)
                {
                    tcs.SetCanceled();
                }
                else if (t.IsFaulted)
                {
                    tcs.SetException(t.Exception.InnerExceptions);
                }
                else
                {
                    try
                    {
                        continuation(future);
                        tcs.SetResult(null);
                    }
                    catch (Exception e)
                    {
                        tcs.SetException(e);
                    }
                }
            }, TaskContinuationOptions.ExecuteSynchronously);

        return tcs.Task;
    }    
}
Run Code Online (Sandbox Code Playgroud)