【问题标题】:Using Tasks with conditional continuations使用带有条件延续的任务
【发布时间】:2011-04-11 19:30:30
【问题描述】:

我对如何使用带有条件延续的任务感到有些困惑。

如果我有一个任务,然后我想继续处理成功和错误的任务,然后等待这些任务完成。

void FunctionThrows() {throw new Exception("faulted");}

static void MyTest()
{

    var taskThrows = Task.Factory.StartNew(() => FunctionThrows());

    var onSuccess = taskThrows.ContinueWith(
                          prev => Console.WriteLine("success"), 
                          TaskContinuationOptions.OnlyOnRanToCompleted);

    var onError = taskThrows.ContinueWith(
                          prev => Console.WriteLine(prev.Exception),
                          TaskContinuationOptions.OnlyOnFaulted);

    //so far, so good



    //this throws because onSuccess was cancelled before it was started
    Task.WaitAll(onSuccess, onError);
}

这是进行任务成功/失败分支的首选方式吗?另外,我应该如何加入所有这些任务,假设我创建了一长串延续,每个都有自己的错误处理。

  //for example
  var task1 = Task.Factory.StartNew(() => ...)
  var task1Error = task1.ContinueWith(  //on faulted
  var task2  = task1.ContinueWith(      //on success
  var task2Error = task2.ContinueWith(  //on faulted
  var task3 = task2.ContinueWith(       //on success
  //etc

在这些总是抛出调用WaitAll,因为某些延续将由于TaskContinuationOptions 而被取消,并且在取消的任务上调用Wait 抛出。 如何在不出现“任务已取消”异常的情况下加入这些?

【问题讨论】:

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


    【解决方案1】:

    我认为您的主要问题是您通过调用来告诉这两个任务“等待”

    Task.WaitAll(onSuccess, onError);
    

    onSuccessonError 延续会自动为您设置,并将在其先前的任务完成后执行

    如果你只是用taskThrows.Start(); 替换你的Task.WaitAll(...),我相信你会得到想要的输出。

    这是我整理的一个例子:

    class Program
    {
        static int DivideBy(int divisor) 
        { 
          Thread.Sleep(2000);
          return 10 / divisor; 
        }
    
        static void Main(string[] args)
        {
            const int value = 0;
    
            var exceptionTask = new Task<int>(() => DivideBy(value));
    
            exceptionTask.ContinueWith(result => Console.WriteLine("Faulted ..."), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent);
            exceptionTask.ContinueWith(result => Console.WriteLine("Success ..."), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent);
    
            exceptionTask.Start();
    
            try
            {
                exceptionTask.Wait();
            }
            catch (AggregateException ex)
            {
                Console.WriteLine("Exception: {0}", ex.InnerException.Message);
            }
    
            Console.WriteLine("Press <Enter> to continue ...");
            Console.ReadLine();
        }
    }
    

    【讨论】:

    • 关键是我想等待任务,以及任何继续完成(假设我想开始任务,做其他事情,然后加入)。由于我不知道前面的任务是成功还是抛出,所以我无法知道该等待哪个。这似乎是一个设计缺陷。
    • 有趣,但我确实注意到 ContinuesWith 返回 another 任务实例。您的代码创建 3 并运行 1,我有点困惑。
    • @dan - 我仍然不完全确定您要“等待”什么?我的两个任务正在等待它们的前身(exceptionTask)完成,然后其中一个任务才会执行。在我上面提供的代码中,两个“等待”任务是错误和成功。如果“exceptionTask”任务失败,则将运行一项任务(Faulted)。如果“exceptionTask”成功,则另一个将运行(成功)。最终,您有 3 个任务。父级,它是两个“依赖”任务...
    • @Henk - 请注意,#ContinueWith() 方法采用可选参数 TaskContinuationOptions,在末尾说明它们应该何时执行。
    • @bryan 我知道在前件完成之前延续不会开始,但我想等待延续完成。有没有办法做到这一点?
    【解决方案2】:

    使用Task.WaitAny(onSuccess, onError);

    【讨论】:

      【解决方案3】:

      这不正常吗?

      查看 MSDN 文档,您做得很好,并且您实现的逻辑是合理的。您唯一缺少的是将 WaitAll 调用包装在 AggregateException 包装器中,如下所示:

      // Exceptions thrown by tasks will be propagated to the main thread
      // while it waits for the tasks. The actual exceptions will be wrapped in AggregateException.
      try
      {
          // Wait for all the tasks to finish.
          Task.WaitAll(tasks);
      
          // We should never get to this point
          Console.WriteLine("WaitAll() has not thrown exceptions. THIS WAS NOT EXPECTED.");
      }
      catch (AggregateException e)
      {
          Console.WriteLine("\nThe following exceptions have been thrown by WaitAll(): (THIS WAS EXPECTED)");
          for (int j = 0; j < e.InnerExceptions.Count; j++)
          {
              Console.WriteLine("\n-------------------------------------------------\n{0}", e.InnerExceptions[j].ToString());
          }
      }
      

      您可以在此处阅读更多内容: http://msdn.microsoft.com/en-us/library/dd270695.aspx

      本质上,捕获 AggregatedException 与完成 WaitAll 相同。它是从您的任务返回的所有异常的集合。

      【讨论】:

      • 我的 onError 任务处理了异常。问题是 onSuccess 任务在它开始之前被取消了。当 WaitAll 对其调用 Wait() 时,会导致它抛出“任务已被取消”异常......这是令人困惑的事情。
      猜你喜欢
      • 1970-01-01
      • 2021-12-27
      • 2016-10-31
      • 1970-01-01
      • 2016-12-28
      • 2023-03-29
      • 2014-05-05
      • 1970-01-01
      • 2017-03-08
      相关资源
      最近更新 更多