Delen via


Gekoppelde en losgekoppelde onderliggende taken

Een onderliggende taak (of geneste taak) is een System.Threading.Tasks.Task exemplaar dat wordt aangemaakt binnen de gebruikersdelegate van een andere taak, die de parenttaak wordt genoemd. Een kindtaak kan losgekoppeld of gekoppeld zijn. Een losgekoppelde kindtaak is een taak die onafhankelijk van de oudertaak wordt uitgevoerd. Een gekoppelde onderliggende taak is een geneste taak die wordt gemaakt met de TaskCreationOptions.AttachedToParent optie, waarbij de bovenliggende taak niet expliciet of standaard verhindert dat de onderliggende taak wordt gekoppeld. Een taak kan een onbeperkt aantal gekoppelde en losgekoppelde onderliggende taken creëren, enkel beperkt door systeembronnen.

De volgende tabel bevat de verschillen tussen de twee soorten subtaken.

Categorie Losgekoppelde onderliggende taken Gekoppelde kindtaken
De ouder wacht tot de kindtaken zijn voltooid. Nee. Ja
Ouder propageert uitzonderingen geworpen door onderliggende taken. Nee. Ja
De status van ouder is afhankelijk van de status van het kind. Nee. Ja

In de meeste scenario's raden we aan gebruik te maken van losgekoppelde kindtaken, omdat hun relaties met andere taken minder complex zijn. Daarom worden taken die in oudertaken zijn gemaakt standaard ontkoppeld, en moet u expliciet de TaskCreationOptions.AttachedToParent optie opgeven om een gekoppelde onderliggende taak te maken.

Losgekoppelde onderliggende taken

Hoewel een onderliggende taak wordt gemaakt door een bovenliggende taak, is deze standaard onafhankelijk van de bovenliggende taak. In het volgende voorbeeld maakt een bovenliggende taak één eenvoudige onderliggende taak. Als u de voorbeeldcode meerdere keren uitvoert, ziet u mogelijk dat de uitvoer van het voorbeeld verschilt van de weergegeven code en dat de uitvoer kan veranderen telkens wanneer u de code uitvoert. Dit gebeurt omdat de hoofdtaak en onderliggende taken onafhankelijk van elkaar worden uitgevoerd; de onderliggende taak is een losgekoppelde taak. In het voorbeeld wordt alleen gewacht totdat de oudertaak is voltooid en de kindtaak mogelijk niet wordt uitgevoerd of voltooid voordat de consoletoepassing wordt beëindigd.

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

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

            Task 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.

Als de onderliggende taak wordt vertegenwoordigd door een Task<TResult> object in plaats van een Task object, kunt u ervoor zorgen dat de bovenliggende taak wacht tot de onderliggende taak is voltooid door toegang te krijgen tot de Task<TResult>.Result eigenschap van de onderliggende taak, zelfs als het een losgekoppelde onderliggende taak is. De Result eigenschap wordt geblokkeerd totdat de taak is voltooid, zoals in het volgende voorbeeld wordt weergegeven.

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

class Example3
{
    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 {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

Gekoppelde kindtaken

In tegenstelling tot losgekoppelde kindtaken, worden gekoppelde kindtaken nauw gesynchroniseerd met de ouder. U kunt de losgekoppelde onderliggende taak in het vorige voorbeeld wijzigen in een gekoppelde onderliggende taak met behulp van de TaskCreationOptions.AttachedToParent optie in de instructie voor het maken van taken, zoals wordt weergegeven in het volgende voorbeeld. In deze code wordt de gekoppelde onderliggende taak voltooid vóór de bovenliggende taak. Als gevolg hiervan is de uitvoer uit het voorbeeld steeds hetzelfde wanneer u de code uitvoert.

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.

U kunt gekoppelde onderliggende taken gebruiken om nauw gesynchroniseerde grafieken van asynchrone bewerkingen te maken.

Een onderliggende taak kan echter alleen aan de bovenliggende taak worden gekoppeld als het bovenliggende item geen gekoppelde onderliggende taken verbiedt. Bovenliggende taken kunnen expliciet voorkomen dat onderliggende taken aan deze taken worden gekoppeld door de TaskCreationOptions.DenyChildAttach optie op te geven in de klasseconstructor van de bovenliggende taak of de TaskFactory.StartNew methode. Bovenliggende taken verhinderen impliciet dat onderliggende taken aan hen worden gekoppeld als ze worden gemaakt door de methode Task.Run aan te roepen. In het volgende voorbeeld ziet u dit. Het is identiek aan het vorige voorbeeld, behalve dat de bovenliggende taak wordt gemaakt door de Task.Run(Action) methode aan te roepen in plaats van de TaskFactory.StartNew(Action) methode. Omdat de onderliggende taak niet aan het bovenliggende item kan worden gekoppeld, is de uitvoer van het voorbeeld onvoorspelbaar. Omdat de standaardopties voor het maken van taken voor de Task.Run overbelastingen zijn, is TaskCreationOptions.DenyChildAttachdit voorbeeld functioneel gelijk aan het eerste voorbeeld in de sectie 'Losgekoppelde onderliggende taken'.

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

public class Example2
{
   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.

Uitzonderingen in onderliggende taken

Als een kindtaak die niet is verbonden een uitzondering genereert, moet die uitzondering direct in de bovenliggende taak worden waargenomen of afgehandeld, net als bij een niet-geneste taak. Als een gekoppelde onderliggende taak een uitzondering genereert, wordt de uitzondering automatisch doorgegeven aan de bovenliggende taak en terug naar de thread die wacht of probeert toegang te krijgen tot de eigenschap van de taak Task<TResult>.Result. Daarom kunt u met behulp van gekoppelde onderliggende taken alle uitzonderingen op slechts één punt in de aanroep afhandelen op de aanroepende Task.Wait thread. Zie Uitzonderingsafhandelingvoor meer informatie.

Annulerings- en onderliggende taken

Taakannulering is coöperatief. Dat wil zeggen dat om annuleerbaar te zijn, elke gekoppelde of losgekoppelde onderliggende taak de status van het annuleringstoken nauwgezet moet controleren. Als u een ouder en al zijn kinderen wilt annuleren met behulp van één annuleringsverzoek, geeft u hetzelfde token als argument door aan alle taken en voorziet u in elke taak in de logica om op het verzoek te reageren. Voor meer informatie, zie Taakannulering en Hoe te: Een taak en zijn kinderen annuleren.

Wanneer de ouder annuleert

Als een ouder zichzelf annuleert voordat zijn kindtaak wordt gestart, begint de kindtaak nooit. Als een oudertaak zichzelf annuleert nadat de kindtaak al is gestart, wordt de kindtaak voltooid, tenzij het een eigen annuleringslogica heeft. Zie Taakannuleringvoor meer informatie.

Wanneer een losgekoppelde onderliggende taak wordt geannuleerd

Als een losgekoppelde onderliggende kindtaak zichzelf annuleert met hetzelfde token dat is doorgegeven aan de bovenliggende taak en de bovenliggende taak niet wacht op de onderliggende kindtaak, zal er geen uitzondering doorgegeven worden, omdat de uitzondering wordt beschouwd als een goedaardige samenwerkingsannulering. Dit gedrag is hetzelfde als die van een taak op het hoogste niveau.

Wanneer een gekoppelde onderliggende taak wordt geannuleerd

Wanneer een gekoppelde onderliggende taak zichzelf annuleert met hetzelfde token dat is doorgegeven aan de bovenliggende taak, wordt een TaskCanceledException doorgegeven aan de samenvoegingsthread in een AggregateException. U moet wachten op de hoofduitvoertaak, zodat u alle normale uitzonderingen kunt afhandelen, naast alle foutieve uitzonderingen die worden doorgegeven via een netwerk van gekoppelde onderliggende taken.

Zie Uitzonderingsafhandelingvoor meer informatie.

Voorkomen dat een kindtaak wordt gekoppeld aan zijn oudertaak

Een niet-verwerkte uitzondering die wordt gegenereerd door een onderliggende taak, wordt doorgegeven aan de bovenliggende taak. U kunt dit gedrag gebruiken om alle onderliggende taakuitzonderingen van één hoofdtaak te observeren in plaats van een structuur van taken te doorlopen. Exceptieoverdracht kan echter problematisch zijn wanneer een bovenliggende taak geen aanhechting van andere code verwacht. Denk bijvoorbeeld aan een app die een bibliotheekonderdeel van derden aanroept vanuit een Task object. Als het bibliotheekonderdeel van derden ook een Task object maakt en TaskCreationOptions.AttachedToParent specificeert om het aan de bovenliggende taak te koppelen, worden eventuele niet-verwerkte uitzonderingen die in de kindtaak voorkomen doorgegeven aan de bovenliggende taak. Dit kan leiden tot onverwacht gedrag in de hoofd-app.

Als u wilt voorkomen dat een onderliggende taak aan de bovenliggende taak wordt gekoppeld, geeft u de TaskCreationOptions.DenyChildAttach optie op wanneer u het bovenliggende Task of Task<TResult> object maakt. Wanneer een taak probeert te koppelen aan het bovenliggende item en het bovenliggende item de TaskCreationOptions.DenyChildAttach optie opgeeft, kan de onderliggende taak niet aan een bovenliggende taak worden gekoppeld en wordt deze uitgevoerd alsof de TaskCreationOptions.AttachedToParent optie niet is opgegeven.

Mogelijk wilt u ook voorkomen dat een kindtaak aan de oudertaak wordt gekoppeld wanneer de kindtaak niet tijdig eindigt. Omdat een bovenliggende taak pas wordt voltooid als alle onderliggende taken zijn voltooid, kan een langlopende onderliggende taak ertoe leiden dat de algehele app slecht presteert. Voor een voorbeeld waarin wordt getoond hoe u de prestaties van apps kunt verbeteren door te voorkomen dat een taak aan de bovenliggende taak wordt gekoppeld, raadpleegt u Procedure: Voorkomen dat een onderliggende taak aan de bovenliggende taak wordt gekoppeld.

Zie ook