Připojené a odpojené podřízené úlohy

Podřízený úkol (nebo vnořený úkol) je System.Threading.Tasks.Task instance vytvořená v delegátu uživatele jiného úkolu, který se označuje jako nadřazený úkol. Podřízený úkol může být odpojen nebo připojen. Odpojený podřízený úkol je úkol , který se spouští nezávisle na nadřazené úloze. Připojený podřízený úkol je vnořený úkol , který je vytvořen s TaskCreationOptions.AttachedToParent možností, jejíž nadřazený objekt explicitně nebo ve výchozím nastavení zakáže jeho připojení. Úkol může vytvořit libovolný počet připojených a odpojených podřízených úkolů, omezený pouze systémovými prostředky.

Následující tabulka uvádí základní rozdíly mezi dvěma druhy podřízených úkolů.

Kategorie Odpojené podřízené úkoly Připojené podřízené úkoly
Nadřazené úkoly čekají na dokončení podřízených úkolů. No Ano
Nadřazené objekty šíří výjimky vyvolané podřízenými úkoly. No Ano
Stav nadřazeného objektu závisí na stavu dítěte. No Ano

Ve většině scénářů doporučujeme používat odpojené podřízené úkoly, protože jejich vztahy s jinými úkoly jsou méně složité. To je důvod, proč jsou úkoly vytvořené uvnitř nadřazených úkolů ve výchozím nastavení odpojeny a je nutné explicitně zadat TaskCreationOptions.AttachedToParent možnost pro vytvoření připojeného podřízeného úkolu.

Odpojené podřízené úkoly

Přestože je podřízený úkol vytvořen nadřazeným úkolem, ve výchozím nastavení je nezávislý na nadřazené úloze. V následujícím příkladu vytvoří nadřazený úkol jeden jednoduchý podřízený úkol. Pokud vzorový kód spustíte několikrát, můžete si všimnout, že se výstup z příkladu liší od zobrazeného příkladu a také že se výstup může při každém spuštění kódu změnit. K tomu dochází, protože nadřazené a podřízené úkoly se provádějí nezávisle na sobě; podřízený úkol je odpojený. Příklad čeká pouze na dokončení nadřazené úlohy a podřízená úloha se nemusí spustit ani dokončit před ukončením konzolové aplikace.

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.

Pokud je podřízený úkol reprezentován objektem Task<TResult> místo objektu Task , můžete zajistit, aby nadřazený úkol čekal na dokončení podřízeného úkolu přístupem k Task<TResult>.Result vlastnosti podřízeného úkolu, i když se jedná o odpojený podřízený úkol. Vlastnost Result blokuje, dokud se jeho úkol neskončí, jak ukazuje následující příklad.

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

Připojené podřízené úkoly

Na rozdíl od odpojených podřízených úkolů jsou připojené podřízené úkoly úzce synchronizovány s nadřazeným objektem. Odpojený podřízený úkol v předchozím příkladu můžete změnit na připojený podřízený úkol pomocí TaskCreationOptions.AttachedToParent možnosti v příkazu vytvoření úkolu, jak je znázorněno v následujícím příkladu. V tomto kódu se připojený podřízený úkol dokončí před nadřazeným objektem. Výsledkem je, že výstup z příkladu je stejný při každém spuštění kódu.

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.

Připojené podřízené úlohy můžete použít k vytvoření úzce synchronizovaných grafů asynchronních operací.

Podřízený úkol se však může připojit k nadřazené sadě pouze v případě, že nadřazený úkol nezakazuje připojené podřízené úkoly. Nadřazené úkoly mohou explicitně zabránit tomu, aby se k nim podřízené úkoly připojovaly zadáním TaskCreationOptions.DenyChildAttach možnosti v konstruktoru TaskFactory.StartNew třídy nadřazeného úkolu nebo metodě. Nadřazené úkoly implicitně brání připojení podřízených úkolů k nim, pokud jsou vytvořeny voláním Task.Run metody. Toto dokládá následující příklad. Je identický s předchozím příkladem s tím rozdílem, že nadřazený úkol je vytvořen voláním Task.Run(Action) metody místo TaskFactory.StartNew(Action) metody. Vzhledem k tomu, že podřízený úkol se nemůže připojit k nadřazené sadě, je výstup z příkladu nepředvídatelný. Vzhledem k tomu, že výchozí možnosti vytváření úkolů pro Task.Run přetížení zahrnují TaskCreationOptions.DenyChildAttach, tento příklad je funkčně ekvivalentní prvnímu příkladu v části Odpojené podřízené úkoly.

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.

Výjimky v podřízených úkolech

Pokud odpojená podřízená úloha vyvolá výjimku, musí být tato výjimka pozorována nebo zpracována přímo v nadřazené úloze stejně jako u jakékoli nenořené úlohy. Pokud připojený podřízený úkol vyvolá výjimku, výjimka se automaticky rozšíří do nadřazené úlohy a zpět do vlákna, které čeká nebo se pokusí o přístup k vlastnosti úkolu Task<TResult>.Result . Proto pomocí připojených podřízených úloh můžete zpracovávat všechny výjimky v jediném bodě volání Task.Wait volajícího vlákna. Další informace naleznete v tématu Zpracování výjimek.

Zrušení a podřízené úkoly

Zrušení úkolu je spolupráce. To znamená, že aby bylo možné zrušit každou připojenou nebo odpojenou podřízenou úlohu, musí monitorovat stav tokenu zrušení. Pokud chcete zrušit nadřazenou položku a všechny její podřízené položky pomocí jedné žádosti o zrušení, předáte všem úkolům stejný token jako argument a v každém úkolu zadáte logiku, která bude odpovídat na požadavek v každém úkolu. Další informace naleznete v tématu Zrušení a postupy úkolu: Zrušení úkolu a jeho podřízených položek.

Když se nadřazený objekt zruší

Pokud se nadřazený objekt zruší před spuštěním podřízeného úkolu, podřízený úkol se nikdy nespustí. Pokud se nadřazený objekt zruší po spuštění podřízené úlohy, spustí se podřízená operace k dokončení, pokud nemá vlastní logiku zrušení. Další informace naleznete v tématu Zrušení úkolu.

Když se zruší odpojená podřízená úloha

Pokud se odpojený podřízený úkol zruší pomocí stejného tokenu, který byl předán nadřazené, a nadřazený úkol nečeká na podřízený úkol, není rozšířena žádná výjimka, protože výjimka je považována za neškodné zrušení spolupráce. Toto chování je stejné jako u každého úkolu nejvyšší úrovně.

Když se připojený podřízený úkol zruší

Když se připojený podřízený úkol zruší pomocí stejného tokenu, který byl předán nadřazené úloze, TaskCanceledException rozšíří se do spojovacího vlákna uvnitř AggregateException. Musíte počkat na nadřazenou úlohu, abyste mohli zpracovávat všechny neškodné výjimky kromě všech chybných výjimek, které se šíří prostřednictvím grafu připojených podřízených úkolů.

Další informace naleznete v tématu Zpracování výjimek.

Zabránění připojení podřízené úlohy k nadřazené úloze

Neošetřená výjimka vyvolaná podřízeným úkolem se rozšíří do nadřazeného úkolu. Toto chování můžete použít k pozorování všech výjimek podřízených úkolů z jednoho kořenového úkolu místo procházení stromu úkolů. Šíření výjimek však může být problematické, pokud nadřazený úkol neočekává přílohu z jiného kódu. Představte si například aplikaci, která volá komponentu knihovny třetí strany z objektu Task . Pokud komponenta knihovny třetí strany také vytvoří Task objekt a určuje TaskCreationOptions.AttachedToParent jeho připojení k nadřazené úloze, všechny neošetřené výjimky, ke kterým dochází v podřízené úloze, se rozšíří do nadřazeného objektu. To může vést k neočekávanému chování v hlavní aplikaci.

Chcete-li zabránit podřízené úloze v připojení k nadřazené úloze, zadejte TaskCreationOptions.DenyChildAttach možnost při vytváření nadřazeného Task objektu nebo Task<TResult> objektu. Když se úkol pokusí připojit ke svému nadřazeného objektu a nadřazený objekt určuje TaskCreationOptions.DenyChildAttach možnost, podřízený úkol se nebude moct připojit k nadřazené sadě a spustí se stejně, jako kdyby TaskCreationOptions.AttachedToParent nebyla zadána.

Můžete také zabránit tomu, aby se podřízený úkol připojil k nadřazené úlohu, když podřízený úkol nedokončí včas. Vzhledem k tomu, že nadřazený úkol se nedokončí, dokud se nedokončí všechny podřízené úkoly, může dlouhotrvající podřízený úkol způsobit špatně výkon celé aplikace. Příklad, který ukazuje, jak zlepšit výkon aplikace tím, že brání úkolu v připojení k nadřazené úloze, najdete v tématu Postupy: Zabránění připojení podřízené úlohy k nadřazené úloze.

Viz také