13

Mam problem ze zrozumieniem, jak działa parametr AttachedToParent.AttachedToParent Zadanie pomyłki

Oto przykładowy kod:

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(); 
    } 

Jak rozumiem, gdy zadanie ma Zadania dziecko, rodzic Zadanie kończy się, gdy wszystkie zadania podrzędne są gotowe. Problem z tym przykładem jest taki, że wyjście wygląda następująco:

0 
0 
0 

Oznacza to, że zadanie nadrzędne nie czekało na zakończenie zadań podrzędnych. Jedynym sposobem, aby uzyskać poprawny wynik 0 1 2 jest użycie Poczekaj na wszystkie dzieci taks, dodając jakiś kawałek kodu jak ten przed stwierdzeniem return results;:

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

Moje pytanie jest takie. Dlaczego używamy TaskCreationOptions.AttachedToParent, kiedy musimy ręcznie wywoływać metodę Wait dla każdego dziecka Zadanie?

Edit:

Chociaż pisałem to pytanie, mam zmieniony kod trochę i teraz AttachedToParent działa dobrze. Jedyna różnica polega na tym, że użyłem parentTask.Start(); zamiast 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(); 
    } 

Nadal nie rozumiem, dlaczego występuje problem z pierwszym przykładem.

Odpowiedz

19

Spójrz na tym blogu: Task.Run vs Task.Factory.StartNew

Pierwszy przykład:

Task.Run(someAction); 

jest uproszczony odpowiednik metody:

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

Zrobiłem trochę badań, przy użyciu reflektor, tutaj jest źródło metody 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); 
    } 

Ważnym parametrem metody Task.Factory.StartNew jest TaskCreationOptions creationOptions. W metodzie Task.Factory.StartNew ten parametr jest równy TaskCreationOptions.DenyChildAttach. To znaczy, że

InvalidOperationException zostanie wyrzucony jeśli próbuje się dołączyć zadanie dziecko do utworzonego zadania

trzeba zmienić, aby osiągnąć właściwą TaskCreationOptions.None zachowanie kodu.

Metoda Task.Runnie zapewnia możliwości zmiany parametru TaskCreationOptions.

+0

* DenyChildAttach * wydaje się być problemem. Kiedy tworzę zadanie nadrzędne w następujący sposób: 'TaskFactory tf = new TaskFactory (TaskCreationOptions.DenyChildAttach, TaskContinuationOptions.None); Zadanie parentTask = tf.StartNew (() => {....}); 'Otrzymuję ten sam problem i kiedy używam * TaskCreationOptions.None * działa dobrze. –