13

我在理解AttachedToParent參數如何工作時遇到問題。AttachedToParent任務混淆

下面是示例代碼:

public static void Main(string[] args) 
    { 
     Task<int[]> parentTask = Task.Run(()=> 
     { 
      int[] results = new int[3]; 

      Task t1 = new Task(() => { Thread.Sleep(3000); results[0] = 0; }, TaskCreationOptions.AttachedToParent); 
      Task t2 = new Task(() => { Thread.Sleep(3000); results[1] = 1; }, TaskCreationOptions.AttachedToParent); 
      Task t3 = new Task(() => { Thread.Sleep(3000); results[2] = 2; }, TaskCreationOptions.AttachedToParent); 

      t1.Start(); 
      t2.Start(); 
      t3.Start(); 

      return results; 
     }); 

     Task finalTask = parentTask.ContinueWith(parent => 
     { 
      foreach (int result in parent.Result) 
      { 
       Console.WriteLine(result); 
      } 
     }); 

     finalTask.Wait(); 
     Console.ReadLine(); 
    } 

據我瞭解,當任務有子任務,當所有的子任務準備父任務完成。這個例子的問題是,輸出看起來像這樣:

0 
0 
0 

這意味着父任務沒有等待其子任務完成。得到一個有效的結果0 1 2的唯一方法是使用等待所有孩子TAKS的,由return results;語句之前增加了一些一段這樣的代碼:

Task[] taskList = { t1, t2, t3 }; 
Task.WaitAll(taskList); 

我的問題是這樣的。爲什麼我們在使用TaskCreationOptions.AttachedToParent時還必須手動爲每個子任務調用Wait方法?

編輯:

當我在寫這個問題,我已經改變了代碼一點點,現在AttachedToParent效果很好。唯一的區別是我用parentTask.Start();而不是Task.Run();

public static void Main(string[] args) 
    { 
     Task<int[]> parentTask = new Task<int[]>(()=> 
     { 
      int[] results = new int[3]; 

      Task t1 = new Task(() => { Thread.Sleep(3000); results[0] = 0; }, TaskCreationOptions.AttachedToParent); 
      Task t2 = new Task(() => { Thread.Sleep(3000); results[1] = 1; }, TaskCreationOptions.AttachedToParent); 
      Task t3 = new Task(() => { Thread.Sleep(3000); results[2] = 2; }, TaskCreationOptions.AttachedToParent); 

      t1.Start(); 
      t2.Start(); 
      t3.Start(); 

      //Task[] taskList = { t1, t2, t3 }; 
      //Task.WaitAll(taskList); 

      return results; 
     }); 

     parentTask.Start(); 

     Task finalTask = parentTask.ContinueWith(parent => 
     { 
      foreach (int result in parent.Result) 
      { 
       Console.WriteLine(result); 
      } 
     }); 

     finalTask.Wait(); 
     Console.ReadLine(); 
    } 

我還是不明白爲什麼第一個例子有問題。

回答

19

看看這個博客帖子:Task.Run vs Task.Factory.StartNew

第一個例子:

Task.Run(someAction); 

簡化方法的等效:

Task.Factory.StartNew(someAction, 
     CancellationToken.None, 
     TaskCreationOptions.DenyChildAttach, 
     TaskScheduler.Default); 

我做了一點研究,利用反射鏡,這裏是一個方法來源Task.Run

public static Task Run(Func<Task> function, CancellationToken cancellationToken) 
    { 
     if (function == null) 
     throw new ArgumentNullException("function"); 
     cancellationToken.ThrowIfSourceDisposed(); 
     if (cancellationToken.IsCancellationRequested) 
     return Task.FromCancellation(cancellationToken); 
     else 
     return (Task) new UnwrapPromise<VoidTaskResult>(
      (Task) Task<Task>.Factory.StartNew(function, 
            cancellationToken, 
            TaskCreationOptions.DenyChildAttach, 
            TaskScheduler.Default), 
      true); 
    } 

方法Task.Factory.StartNew的重要參數是TaskCreationOptions creationOptions。方法Task.Factory.StartNew該參數等於TaskCreationOptions.DenyChildAttach。這意味着,如果試圖以 做出附加子任務到創建的任務

您需要更改爲TaskCreationOptions.None實現代碼的正確行爲

一個InvalidOperationException將被拋出。

方法Task.Run不提供更改TaskCreationOptions參數的功能。

+0

* DenyChildAttach *真的好像是問題所在。當我創建像這樣的父任務:'TaskFactory tf =新的TaskFactory(TaskCreationOptions.DenyChildAttach,TaskContinuationOptions.None); 任務 parentTask = tf.StartNew(()=> {....});'我得到同樣的問題,當我使用* TaskCreationOptions.None *時,它工作正常。 –