Sdílet prostřednictvím


Pokračující úlohy

V asynchronním programování je velmi běžné, že jedna asynchronní operace při dokončení vyvolá druhou operaci a předá ji data. Tradičně bylo toto prováděno pomocí metod zpětného volání. V Task Parallel Library je stejná funkčnost zajištěna pomocí pokračujících úloh. Pokračování úlohy (známé také jako pokračování právě) je asynchronní úkol, který je vyvolán jiný úkol, který je označován jako Vlastnost antecedent, po dokončení antecedent.

Pokračování jsou relativně snadno použitelná, ale přesto jsou poměrně výkonná a flexibilní. Můžete například:

  • předat data z předchůdce do pokračování

  • určit přesné podmínky, za kterých bude či nebude pokračování vyvoláno

  • zrušit pokračování před spuštěním nebo kooperativně za běhu

  • poskytnout nápovědu, jak má být pokračování plánováno

  • vyvolat ze stejného předchůdce více pokračování

  • vyvolat jedno pokračování až skončí všichni nebo jen někteří z více předchůdců

  • zřetězit pokračování jedno po druhém do jakékoli libovolné délky

  • použít pokračování pro zpracování výjimek vyvolaných předchůdcem

Pokračování lze vytvářet pomocí metody Task.ContinueWith. Následující příklad zobrazuje základní vzor (pro přehlednost je vynecháno zpracování výjimek).

' The antecedent task. Can also be created with Task.Factory.StartNew.
Dim taskA As Task(Of DayOfWeek) = New Task(Of DayOfWeek)(Function()
                                                             Return DateTime.Today.DayOfWeek
                                                         End Function)
' The continuation. Its delegate takes the antecedent task
' as an argument and can return a different type.
Dim continuation As Task(Of String) = taskA.ContinueWith(Function(antecedent)
                                                             Return String.Format("Today is {0}", antecedent.Result)
                                                         End Function)
' Start the antecedent.
taskA.Start()

' Use the contuation's result.
Console.WriteLine(continuation.Result)
            // The antecedent task. Can also be created with Task.Factory.StartNew.
            Task<DayOfWeek> taskA = new Task<DayOfWeek>(() => DateTime.Today.DayOfWeek);

            // The continuation. Its delegate takes the antecedent task
            // as an argument and can return a different type.
            Task<string> continuation = taskA.ContinueWith((antecedent) =>
                {
                    return String.Format("Today is {0}.",
                                        antecedent.Result);
                });

            // Start the antecedent.
            taskA.Start();

            // Use the contuation's result.
            Console.WriteLine(continuation.Result);

Lze také vytvořit víceúlohové pokračování, které bude spuštěno po dokončení některých nebo všech úloh v poli, jak je znázorněno v následujícím příkladu.

Dim task1 As Task(Of Integer) = New Task(Of Integer)(Function()
                                                         ' Do some work...
                                                         Return 34
                                                     End Function)

Dim task2 As Task(Of Integer) = New Task(Of Integer)(Function()
                                                         ' Do some work...
                                                         Return 8
                                                     End Function)

Dim tasks() As Task(Of Integer) = {task1, task2}

Dim continuation = Task.Factory.ContinueWhenAll(tasks, Sub(antecedents)
                                                           Dim answer As Integer = tasks(0).Result + tasks(1).Result
                                                           Console.WriteLine("The answer is {0}", answer)


                                                       End Sub)
task1.Start()
task2.Start()
continuation.Wait()
            Task<int>[] tasks = new Task<int>[2];
            tasks[0] = new Task<int>(() =>
            {
                // Do some work... 
                return 34;
            });

            tasks[1] = new Task<int>(() =>
            {
                // Do some work...
                 return 8;
            });

            var continuation = Task.Factory.ContinueWhenAll(
                            tasks,
                            (antecedents) =>
                            {
                                int answer = tasks[0].Result + tasks[1].Result;
                                Console.WriteLine("The answer is {0}", answer);
                            });

            tasks[0].Start();
            tasks[1].Start();
            continuation.Wait();

Pokračování je vytvořeno ve stavu WaitingForActivation, a proto může být spuštěno pouze svou předchozí úlohou. Volání Task.Start na pokračování v uživatelském kódu vyvolá výjimku System.InvalidOperationException.

Pokračování je sám Task a blokovat vlákno, na kterém je spuštěna. Pomocí metody Počkejte až do dokončení úkolu pokračování blokovat.

Možnosti pokračování

Při vytváření pokračování jedné úlohy lze použít přetížení ContinueWith, které přijímá výčet System.Threading.Tasks.TaskContinuationOptions pro určení podmínek, za kterých předchozí úloha pokračování spustí. Lze například určit, že pokračování bude spuštěno pouze v případě, že byl předchůdce úspěšně dokončen nebo pouze tehdy, pokud skončil chybovým stavem a tak dále. Pokud není podmínka splněna ve chvíli, kdy je předchůdce připraven k vyvolání pokračování, pokračování přechází přímo do stavu Canceled a již nemůže být dále spuštěno. Pokud je zadána některá z možností NotOn nebo OnlyOn s víceúlohovým pokračováním, bude vyvolána výjimka za běhu.

Výčet System.Threading.Tasks.TaskContinuationOptions zahrnuje také stejné možnosti jako výčet System.Threading.Tasks.TaskCreationOptions. AttachedToParent, LongRunning a PreferFairness mají stejný význam i hodnoty v obou výčtových typech. Tyto možnosti lze použít s víceúlohovými pokračováními.

V následující tabulce jsou uvedeny všechny hodnoty pro TaskContinuationOptions.

Prvek

Popis

None

Určuje výchozí chování, pokud nejsou zadány žádné TaskContinuationOptions. Bez ohledu na konečný stav předchůdce bude pokračování naplánováno po dokončení předchůdce. Pokud je úloha podřízená úloha, je vytvořena jako samostatná vnořená úloha.

PreferFairness

Určuje, že pokračování by měly být naplánovány tak, že pokračování vytvořené dříve se budou pravděpodobněji spouštět dříve a později vytvořené pokračování se budou s větší pravděpodobností spouštět později.

LongRunning

Určuje, že pokračování bude dlouhotrvající, tzv. course-grained operace. Poskytuje nápovědu, která System.Threading.Tasks.TaskScheduler přiznat že zaznamenán.

AttachedToParent

Určuje, že pokračování, pokud je podřízenou úlohou, je připojeno k nadřízené úloze v hierarchii úloh. Pokračování je podřízená úloha pouze v případě, je-li jeho předchůdce také podřízená úloha.

NotOnRanToCompletion

Určuje, že pokračování nemá být naplánováno v případě, že byl jeho předchůdce úspěšně dokončen.

NotOnFaulted

Určuje, že pokračování nemá být naplánováno, pokud v jeho předchůdci došlo k neošetřené výjimce.

NotOnCanceled

Určuje, že pokračování nemá být naplánováno, pokud byl jeho předchůdce zrušen.

OnlyOnRanToCompletion

Určuje, že pokračování má být naplánováno pouze v případě, že byl jeho předchůdce úspěšně dokončen.

OnlyOnFaulted

Určuje, že pokračování má být naplánováno, pouze pokud v jeho předchůdci došlo k neošetřené výjimce. Při použití OnlyOnFaulted možnost, bylo zaručeno, že Exception Vlastnost antecedent nejsou null. Tuto vlastnost lze použít k zachycení výjimky a zjištění, která výjimka způsobila selhání úlohy. Pokud nepřistoupíte k vlastnosti Exception, je výjimka neošetřená. Také pokud se pokusíte o přístup k vlastnosti Result úlohy, která byla zrušena nebo v ní došlo k chybě, bude vyvolána nová výjimka.

OnlyOnCanceled

Určuje, že pokračování má být naplánováno pouze v případě, že jeho předchůdce skončí ve stavu Canceled.

ExecuteSynchronously

Pro velmi krátce běžící pokračování. Určuje, že by pokračování v ideálním případě mělo běžet ve stejném vlákně, které způsobuje přechod předchůdce do konečného stavu. Pokud je při vytvoření pokračování předchůdce již dokončen, systém se pokusí spustit pokračování ve vlákně, které pokračování vytváří. Pokud k předchozímu CancellationTokenSource odbyt v finally bloku (Finally v jazyce Visual Basic), pokračování této možnosti bude spuštěn v tomto finally bloku.

Předávání údajů do pokračování

Uživatelskému delegátovi pokračování je jako argument předána reference na předchůdce. Pokud je předchůdce typu System.Threading.Tasks.Task<TResult> a úloha byla dokončena, pak může pokračování přistupovat k vlastnosti Task<TResult>.Result úlohy. V případě víceúlohového pokračování a metody Task.WaitAll je argumentem pole předchůdců. Při použití Task.WaitAny je argumentem první předchůdce, který byl dokončen.

Task<TResult>.Result blokuje dokud není úloha dokončena. Avšak pokud byla úloha zrušena nebo došlo k chybě, pak Result vyvolá výjimku při pokusu kódu o přístup k této vlastnosti. Tomuto problému se lze vyhnout pomocí možnosti OnlyOnRanToCompletion, jak je znázorněno v následujícím příkladu.

            Dim aTask = Task(Of Integer).Factory.StartNew(Function()
                                                              Return 54
                                                          End Function)
            Dim bTask = aTask.ContinueWith(Sub(antecedent)
                                               Console.WriteLine("continuation {0}", antecedent.Result)
                                           End Sub,
                                           TaskContinuationOptions.OnlyOnRanToCompletion)

var t = Task<int>.Factory.StartNew(() => 54);

var c = t.ContinueWith((antecedent) =>
{
    Console.WriteLine("continuation {0}", antecedent.Result);
},
    TaskContinuationOptions.OnlyOnRanToCompletion);

Pokud potřebujete, aby bylo pokračování spuštěno i v případě, že předchůdce nebyl úspěšně dokončen, pak je nutno se proti výjimce chránit. Jedním z možných způsobů je ověřit stav předchůdce a přistupovat k Result pouze pokud stav není Faulted nebo Canceled. Také lze zkoumat vlastnost Exception předchůdce. Další informace naleznete v tématu Zpracovávání vyjímek (Task Parallel Library).

Zrušení pokračování

Pokračování přejde do stavu Canceled v těchto situacích:

  • Pokud vyvolá OperationCanceledException v odpovědi na žádost o zrušení. Stejně jako u jakékoli jiné úlohy, pokud výjimka obsahuje stejný token, který byl předán pokračování, bude zpracována jako potvrzení kooperativního zrušení.

  • Při pokračování byl předán System.Threading.CancellationToken jako argument a IsCancellationRequested Vlastnost tokenu je true ()True) před pokračování spustí. V takovém případě se pokračování nespustí a přechází přímo do stavu Canceled.

  • Pokud se pokračování nikdy nespustí, protože podmínka, nastavená v jeho TaskContinuationOptions, nebyla splněna. Například pokud úloha přejde do stavu Faulted, její pokračování, které bylo vytvořeno pomocí možnosti NotOnFaulted přejde do stavu Canceled a nebude spuštěno.

Chcete-li zabránit spuštění pokračování, pokud je zrušen předchůdce, zadejte při vytváření pokračování možnost NotOnCanceled.

Pokud úloha a její pokračování představují dvě části stejné logické operace, lze předat stejný token zrušení oběma úkolům, jak je znázorněno v následujícím příkladu.

Dim someCondition As Boolean = True
Dim cts As New CancellationTokenSource
Dim task1 = New Task(Sub()
                         Dim ct As CancellationToken = cts.Token
                         While someCondition = True
                             ct.ThrowIfCancellationRequested()
                             ' Do the work here...
                             ' ...
                         End While
                     End Sub,
                     cts.Token
                     )

Dim task2 = task1.ContinueWith(Sub(antecedent)
                                   Dim ct As CancellationToken = cts.Token
                                   While someCondition = True
                                       ct.ThrowIfCancellationRequested()
                                       ' Do the work here
                                       ' ...
                                   End While
                               End Sub,
                               cts.Token)
task1.Start()
' ...
' Antecedent and/or continuation will
' respond to this request, depending on when it is made.
cts.Cancel()
Task task = new Task(() =>
{
    CancellationToken ct = cts.Token;
    while (someCondition)
    {
        ct.ThrowIfCancellationRequested();
        // Do the work.
        //...                        
    }
},
    cts.Token
    );

Task task2 = task.ContinueWith((antecedent) =>
{
    CancellationToken ct = cts.Token;

    while (someCondition)
    {
        ct.ThrowIfCancellationRequested();
        // Do the work.
        //...                        
    }
},
    cts.Token);

task.Start();
//...

// Antecedent and/or continuation will 
// respond to this request, depending on when it is made.
cts.Cancel();

Pokud předchůdce nebyl zrušen, token je stále možné použít pro zrušení pokračování. Pokud byl předchůdce zrušen, pokračování nebude spuštěno.

Přechod pokračování do stavu Canceled může ovlivnit pokračování, které následují, v závislosti na nastavení TaskContinuationOptions, které bylo zadáno pro tyto pokračování.

Pokračovaní, které jsou uvolněny, nebudou spuštěny.

Pokračování a podřízené úlohy

Pokračování není nespuštěno do chvíle, než je dokončen předchůdce a všechny jeho připojené podřízené úlohy. Pokračování nečeká na dokončení odpojených podřízených úloh. Konečný stav předchozí úlohy je závislý na konečném stavu všech připojených podřízených úloh. Stav odpojených podřízených úloh nemá na nadřízenou úlohu vliv. Další informace naleznete v tématu Vnořené a podřízené úlohy.

Zpracování výjimek vyvolaných z pokračování

Vztah předchůdce-pokračování není vztah nadřízený-podřízený. Výjimky vyvolané pokračováním nejsou šířeny do předchůdce. Proto je potřeba zpracovávat výjimky vyvolané v pokračování stejně jako v libovolné jiné úloze a to následovně.

  1. Pro čekání na pokračování použít metody Wait, WaitAll nebo WaitAny nebo jejich obecné protějšky. Na předchůdce a jeho pokračování lze čekat ve stejném bloku try (Try v jazyce Visual Basic), jak je znázorněno v následujícím příkladu.
Dim task1 = Task(Of Integer).Factory.StartNew(Function()
                                                  Return 54
                                              End Function)
Dim continuation = task1.ContinueWith(Sub(antecedent)
                                          Console.WriteLine("continuation {0}", antecedent.Result)
                                          Throw New InvalidOperationException()
                                      End Sub)

Try
    task1.Wait()
    continuation.Wait()
Catch ae As AggregateException
    For Each ex In ae.InnerExceptions
        Console.WriteLine(ex.Message)
    Next
End Try

Console.WriteLine("Exception handled. Let's move on.")
var t = Task<int>.Factory.StartNew(() => 54);

var c = t.ContinueWith((antecedent) =>
{
    Console.WriteLine("continuation {0}", antecedent.Result);
    throw new InvalidOperationException();
});

try
{
    t.Wait();
    c.Wait();
}

catch (AggregateException ae)
{
    foreach(var e in ae.InnerExceptions)
        Console.WriteLine(e.Message);
}
Console.WriteLine("Exception handled. Let's move on.");
  1. Pomocí druhého pokračování sledovat vlastnost Exception prvního pokračování. Další informace naleznete v tématu Zpracovávání vyjímek (Task Parallel Library) a Postupy: Zpracování výjimek vyvolaných úlohou.

  2. Je-li pokračování podřízená úloha a bylo vytvořeno s použitím možnosti AttachedToParent, pak budou jeho výjimky šířeny přes nadřízenou úlohu zpět do volajícího vlákna, stejně jako v případě v libovolného jiného připojeného podřízeného. Další informace naleznete v tématu Vnořené a podřízené úlohy.

Viz také

Koncepty

Knihovna paralelních úloh

Historie změn

Datum

Poslední dokumenty

Důvod

Červen 2010

Byla přidána poznámka o asynchronní chování continuations.

Názory zákazníků