Dołączone i odłączone zadania podrzędne

Podrzędne zadanie (lub zadanie zagnieżdżone) jest wystąpieniem utworzonym System.Threading.Tasks.Task w delegatu użytkownika innego zadania, nazywanego zadaniem nadrzędnym. Podrzędne zadanie może być odłączone lub dołączone. Odłączone zadanie podrzędne to zadanie , które wykonuje niezależnie od elementu nadrzędnego. Dołączone zadanie podrzędne to zagnieżdżone zadanie, które jest tworzone z opcjąTaskCreationOptions.AttachedToParent, której element nadrzędny nie jawnie lub domyślnie uniemożliwia mu dołączanie. Zadanie może utworzyć dowolną liczbę dołączonych i odłączonych zadań podrzędnych, ograniczonych tylko przez zasoby systemowe.

W poniższej tabeli wymieniono podstawowe różnice między dwoma rodzajami zadań podrzędnych.

Kategoria Odłączone zadania podrzędne Dołączone zadania podrzędne
Element nadrzędny czeka na ukończenie zadań podrzędnych. Nie. Tak
Element nadrzędny propaguje wyjątki zgłaszane przez zadania podrzędne. Nie. Tak
Stan elementu nadrzędnego zależy od stanu elementu podrzędnego. Nie. Tak

W większości scenariuszy zalecamy używanie odłączonych zadań podrzędnych, ponieważ ich relacje z innymi zadaniami są mniej złożone. Dlatego zadania utworzone wewnątrz zadań nadrzędnych są domyślnie odłączane i należy jawnie określić TaskCreationOptions.AttachedToParent opcję utworzenia dołączonego zadania podrzędnego.

Odłączone zadania podrzędne

Chociaż zadanie podrzędne jest tworzone przez zadanie nadrzędne, domyślnie jest niezależne od zadania nadrzędnego. W poniższym przykładzie zadanie nadrzędne tworzy jedno proste zadanie podrzędne. Jeśli uruchomisz przykładowy kod wiele razy, możesz zauważyć, że dane wyjściowe z przykładu różnią się od pokazanego, a także że dane wyjściowe mogą ulec zmianie za każdym razem, gdy uruchomisz kod. Dzieje się tak, ponieważ zadania nadrzędne i podrzędne są wykonywane niezależnie od siebie; podrzędne jest odłączonym zadaniem. Przykład oczekuje tylko na ukończenie zadania nadrzędnego, a podrzędne zadanie może nie zostać wykonane lub ukończone przed zakończeniem działania aplikacji konsolowej.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var parent = Task.Factory.StartNew(() => {
         Console.WriteLine("Outer task executing.");

         var child = Task.Factory.StartNew(() => {
            Console.WriteLine("Nested task starting.");
            Thread.SpinWait(500000);
            Console.WriteLine("Nested task completing.");
         });
      });

      parent.Wait();
      Console.WriteLine("Outer has completed.");
   }
}
// The example produces output like the following:
//        Outer task executing.
//        Nested task starting.
//        Outer has completed.
//        Nested task completing.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim parent = Task.Factory.StartNew(Sub()
                                               Console.WriteLine("Outer task executing.")
                                               Dim child = Task.Factory.StartNew(Sub()
                                                                                     Console.WriteLine("Nested task starting.")
                                                                                     Thread.SpinWait(500000)
                                                                                     Console.WriteLine("Nested task completing.")
                                                                                 End Sub)
                                           End Sub)
        parent.Wait()
        Console.WriteLine("Outer task has completed.")
    End Sub
End Module
' The example produces output like the following:
'   Outer task executing.
'   Nested task starting.
'   Outer task has completed.
'   Nested task completing.

Jeśli zadanie podrzędne jest reprezentowane przez Task<TResult> obiekt, a nie Task obiekt, możesz upewnić się, że zadanie nadrzędne będzie czekać na ukończenie podrzędnego, korzystając Task<TResult>.Result z właściwości podrzędnej elementu podrzędnego, nawet jeśli jest to odłączone zadanie podrzędne. Właściwość Result blokuje się do momentu ukończenia zadania, jak pokazano w poniższym przykładzie.

using System;
using System.Threading;
using System.Threading.Tasks;

class Example
{
   static void Main()
   {
      var outer = Task<int>.Factory.StartNew(() => {
            Console.WriteLine("Outer task executing.");

            var nested = Task<int>.Factory.StartNew(() => {
                  Console.WriteLine("Nested task starting.");
                  Thread.SpinWait(5000000);
                  Console.WriteLine("Nested task completing.");
                  return 42;
            });

            // Parent will wait for this detached child.
            return nested.Result;
      });

      Console.WriteLine("Outer has returned {0}.", outer.Result);
   }
}
// The example displays the following output:
//       Outer task executing.
//       Nested task starting.
//       Nested task completing.
//       Outer has returned 42.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim parent = Task(Of Integer).Factory.StartNew(Function()
                                                           Console.WriteLine("Outer task executing.")
                                                           Dim child = Task(Of Integer).Factory.StartNew(Function()
                                                                                                             Console.WriteLine("Nested task starting.")
                                                                                                             Thread.SpinWait(5000000)
                                                                                                             Console.WriteLine("Nested task completing.")
                                                                                                             Return 42
                                                                                                         End Function)
                                                           Return child.Result


                                                       End Function)
        Console.WriteLine("Outer has returned {0}", parent.Result)
    End Sub
End Module
' The example displays the following output:
'       Outer task executing.
'       Nested task starting.
'       Detached task completing.
'       Outer has returned 42

Dołączone zadania podrzędne

W przeciwieństwie do odłączonych zadań podrzędnych dołączone zadania podrzędne są ściśle synchronizowane z elementem nadrzędnym. W poprzednim przykładzie można zmienić odłączone zadanie podrzędne na dołączone zadanie podrzędne przy użyciu TaskCreationOptions.AttachedToParent opcji w instrukcji tworzenia zadania, jak pokazano w poniższym przykładzie. W tym kodzie dołączone zadanie podrzędne zostanie ukończone przed jego elementem nadrzędnym. W rezultacie dane wyjściowe z przykładu są takie same przy każdym uruchomieniu kodu.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var parent = Task.Factory.StartNew(() => {
            Console.WriteLine("Parent task executing.");
            var child = Task.Factory.StartNew(() => {
                  Console.WriteLine("Attached child starting.");
                  Thread.SpinWait(5000000);
                  Console.WriteLine("Attached child completing.");
            }, TaskCreationOptions.AttachedToParent);
      });
      parent.Wait();
      Console.WriteLine("Parent has completed.");
   }
}
// The example displays the following output:
//       Parent task executing.
//       Attached child starting.
//       Attached child completing.
//       Parent has completed.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim parent = Task.Factory.StartNew(Sub()
                                               Console.WriteLine("Parent task executing")
                                               Dim child = Task.Factory.StartNew(Sub()
                                                                                     Console.WriteLine("Attached child starting.")
                                                                                     Thread.SpinWait(5000000)
                                                                                     Console.WriteLine("Attached child completing.")
                                                                                 End Sub, TaskCreationOptions.AttachedToParent)
                                           End Sub)
        parent.Wait()
        Console.WriteLine("Parent has completed.")
    End Sub
End Module
' The example displays the following output:
'       Parent task executing.
'       Attached child starting.
'       Attached child completing.
'       Parent has completed.

Za pomocą dołączonych zadań podrzędnych można tworzyć ściśle synchronizowane wykresy operacji asynchronicznych.

Jednak zadanie podrzędne może zostać dołączone do elementu nadrzędnego tylko wtedy, gdy jego element nadrzędny nie zabrania dołączonych zadań podrzędnych. Zadania nadrzędne mogą jawnie uniemożliwić dołączanie podrzędnych zadań podrzędnych przez określenie TaskCreationOptions.DenyChildAttach opcji w konstruktorze klasy zadania nadrzędnego lub metodzie TaskFactory.StartNew . Zadania nadrzędne niejawnie uniemożliwiają dołączanie do nich zadań podrzędnych, jeśli są tworzone przez wywołanie Task.Run metody . Ilustruje to poniższy przykład. Jest on identyczny z poprzednim przykładem, z tą różnicą, że zadanie nadrzędne jest tworzone przez wywołanie Task.Run(Action) metody, a nie TaskFactory.StartNew(Action) metody. Ponieważ zadanie podrzędne nie jest w stanie dołączyć do elementu nadrzędnego, dane wyjściowe z przykładu są nieprzewidywalne. Ponieważ domyślne opcje tworzenia zadań dla Task.Run przeciążeń obejmują TaskCreationOptions.DenyChildAttach, ten przykład jest funkcjonalnie równoważny z pierwszym przykładem w sekcji "Odłączone zadania podrzędne".

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var parent = Task.Run(() => {
            Console.WriteLine("Parent task executing.");
            var child = Task.Factory.StartNew(() => {
                  Console.WriteLine("Attached child starting.");
                  Thread.SpinWait(5000000);
                  Console.WriteLine("Attached child completing.");
            }, TaskCreationOptions.AttachedToParent);
      });
      parent.Wait();
      Console.WriteLine("Parent has completed.");
   }
}
// The example displays output like the following:
//       Parent task executing.
//       Parent has completed.
//       Attached child starting.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim parent = Task.Run(Sub()
                                  Console.WriteLine("Parent task executing.")
                                  Dim child = Task.Factory.StartNew(Sub()
                                                                        Console.WriteLine("Attached child starting.")
                                                                        Thread.SpinWait(5000000)
                                                                        Console.WriteLine("Attached child completing.")
                                                                    End Sub, TaskCreationOptions.AttachedToParent)
                              End Sub)
        parent.Wait()
        Console.WriteLine("Parent has completed.")
    End Sub
End Module
' The example displays output like the following:
'       Parent task executing.
'       Parent has completed.
'       Attached child starting.

Wyjątki w zadaniach podrzędnych

Jeśli odłączone zadanie podrzędne zgłasza wyjątek, ten wyjątek musi być zaobserwowany lub obsłużony bezpośrednio w zadaniu nadrzędnym tak samo jak w przypadku każdego zadania niezagnieżdżonego. Jeśli dołączone zadanie podrzędne zgłasza wyjątek, wyjątek jest automatycznie propagowany do zadania nadrzędnego i z powrotem do wątku, który czeka lub próbuje uzyskać dostęp do właściwości zadania Task<TResult>.Result . W związku z tym przy użyciu dołączonych zadań podrzędnych można obsługiwać wszystkie wyjątki w jednym punkcie wywołania Task.Wait elementu w wątku wywołującym. Aby uzyskać więcej informacji, zobacz Obsługa wyjątków.

Anulowanie i zadania podrzędne

Anulowanie zadania jest spółdzielcze. Oznacza to, że aby można je było anulować, każde dołączone lub odłączone zadanie podrzędne musi monitorować stan tokenu anulowania. Jeśli chcesz anulować element nadrzędny i wszystkie jego elementy podrzędne przy użyciu jednego żądania anulowania, przekaż ten sam token jako argument do wszystkich zadań i podaj w każdym zadaniu logikę, aby odpowiedzieć na żądanie w każdym zadaniu. Aby uzyskać więcej informacji, zobacz Anulowanie zadania i Instrukcje: Anulowanie zadania i jego elementów podrzędnych.

Po anulowaniu elementu nadrzędnego

Jeśli element nadrzędny anuluje się przed uruchomieniem zadania podrzędnego, element podrzędny nigdy się nie uruchamia. Jeśli element nadrzędny anuluje się po jego podrzędnym zadaniu, podrzędne jest uruchamiane do ukończenia, chyba że ma własną logikę anulowania. Aby uzyskać więcej informacji, zobacz Anulowanie zadania.

Gdy odłączone zadanie podrzędne zostanie anulowane

Jeśli odłączone zadanie podrzędne anuluje się przy użyciu tego samego tokenu, który został przekazany do elementu nadrzędnego, a element nadrzędny nie czeka na zadanie podrzędne, nie jest propagowany wyjątek, ponieważ wyjątek jest traktowany jako łagodne anulowanie współpracy. To zachowanie jest takie samo jak w przypadku każdego zadania najwyższego poziomu.

Po anulowaniu dołączonego zadania podrzędnego

Gdy dołączone zadanie podrzędne anuluje się przy użyciu tego samego tokenu, który został przekazany do zadania nadrzędnego, element TaskCanceledException jest propagowany do wątku dołączania wewnątrz AggregateExceptionelementu . Musisz poczekać na zadanie nadrzędne, aby można było obsługiwać wszystkie łagodne wyjątki oprócz wszystkich wyjątków błędów, które są propagowane za pomocą grafu dołączonych zadań podrzędnych.

Aby uzyskać więcej informacji, zobacz Obsługa wyjątków.

Zapobieganie dołączaniu zadania podrzędnego do elementu nadrzędnego

Nieobsługiwany wyjątek zgłaszany przez zadanie podrzędne jest propagowany do zadania nadrzędnego. Tego zachowania można użyć do obserwowania wszystkich wyjątków podrzędnych zadań z jednego zadania głównego zamiast przechodzenia drzewa zadań. Propagacja wyjątków może być jednak problematyczna, gdy zadanie nadrzędne nie oczekuje załącznika z innego kodu. Rozważmy na przykład aplikację, która wywołuje składnik biblioteki innej firmy z Task obiektu. Jeśli składnik biblioteki innej firmy tworzy Task również obiekt i określa TaskCreationOptions.AttachedToParent , aby dołączyć go do zadania nadrzędnego, wszelkie nieobsługiwane wyjątki występujące w zadaniu podrzędnym są propagowane do elementu nadrzędnego. Może to prowadzić do nieoczekiwanego zachowania w głównej aplikacji.

Aby zapobiec dołączaniu zadania podrzędnego do zadania nadrzędnego, określ TaskCreationOptions.DenyChildAttach opcję podczas tworzenia obiektu nadrzędnego Task lub Task<TResult> obiektu. Gdy zadanie próbuje dołączyć do elementu nadrzędnego, a element nadrzędny określa TaskCreationOptions.DenyChildAttach opcję, podrzędne zadanie nie będzie mogło dołączyć do elementu nadrzędnego i zostanie wykonane tak, jakby TaskCreationOptions.AttachedToParent nie określono opcji.

Możesz również uniemożliwić dołączanie podrzędnego zadania podrzędnego do elementu nadrzędnego, gdy zadanie podrzędne nie zostanie ukończone w odpowiednim czasie. Ponieważ zadanie nadrzędne nie zostanie zakończone, dopóki wszystkie zadania podrzędne nie zakończą się, długotrwałe zadanie podrzędne może spowodować, że ogólna aplikacja będzie działać nieprawidłowo. Przykład pokazujący, jak zwiększyć wydajność aplikacji, uniemożliwiając dołączanie zadania nadrzędnego do zadania nadrzędnego, zobacz How to: Prevent a Child Task from Attaching to its Parent (Jak: zapobiegać dołączaniu zadania podrzędnego do elementu nadrzędnego).

Zobacz też