Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
V asynchronním programování je běžné, že jedna asynchronní operace vyvolá druhou operaci při dokončení. Pokračování umožňují následné operace využívat výsledky z první operace. Tradičně se pokračování prováděla pomocí metod zpětného volání. V knihovně TPL (Task Parallel Library) je stejná funkce poskytována úlohami pokračování. Úloha pokračování (známá také jako pokračování) je asynchronní úloha, která je spuštěna jinou úlohou, označovanou jako předchůdce, když se předchůdce dokončí.
Pokračování jsou poměrně snadno použitelná, přičemž jsou výkonná a flexibilní. Například můžete:
- Předejte data z antecedentu do pokračování.
- Zadejte přesné podmínky, za kterých bude pokračování vyvoláno nebo nevyvoláváno.
- Zrušte pokračování buď před tím, než začne, nebo spolupracovaně, jak běží.
- Uveďte rady o tom, jak se má pokračování naplánovat.
- Vyvolat více pokračování ze stejného předchůdce.
- Vyvolejte jedno pokračování, jakmile se dokončí všechny nebo jakýkoliv z více předcházejících úkolů.
- Řetězit pokračování jeden po druhém na libovolnou délku.
- Použití pokračování ke zpracování výjimek vyvolaných objektem antecedent.
O pokračováních
Pokračování je úkol, který je vytvořen ve stavu WaitingForActivation. Aktivuje se automaticky, když se dokončí její předchozí úkol nebo úkoly. Volání Task.Start na pokračování uživatelského kódu vyvolá výjimku System.InvalidOperationException.
Pokračování je vlastně Task samo o sobě a neblokuje vlákno, na němž je spuštěno. Zavolejte metodu Task.Wait, aby blokovala, dokud se úloha s pokračováním nedokončí.
Vytvoření pokračování pro jeden antecedent
Vytvoříte pokračování, které se spustí, když jeho antecedent dokončí voláním Task.ContinueWith metody. Následující příklad ukazuje základní vzor (pro přehlednost je vynecháno zpracování výjimek). Spustí úlohu taskA
, která vrací DayOfWeek objekt, který označuje název aktuálního dne v týdnu. Jakmile taskA
dokončí, antecedent
představuje jeho výsledky v pokračovací metodě ContinueWith
. Výsledek předchozí úlohy je zapsán do konzole.
using System;
using System.Threading.Tasks;
public class SimpleExample
{
public static async Task Main()
{
// Declare, assign, and start the antecedent task.
Task<DayOfWeek> taskA = Task.Run(() => DateTime.Today.DayOfWeek);
// Execute the continuation when the antecedent finishes.
await taskA.ContinueWith(antecedent => Console.WriteLine($"Today is {antecedent.Result}."));
}
}
// The example displays the following output:
// Today is Monday.
Imports System.Threading.Tasks
Module Example
Public Sub Main()
' Execute the antecedent.
Dim taskA As Task(Of DayOfWeek) = Task.Run(Function() DateTime.Today.DayOfWeek)
' Execute the continuation when the antecedent finishes.
Dim continuation As Task = taskA.ContinueWith(Sub(antecedent)
Console.WriteLine("Today is {0}.", antecedent.Result)
End Sub)
continuation.Wait()
End Sub
End Module
' The example displays output like the following output:
' Today is Monday.
Vytvořit pokračování pro více antecedentů
Můžete také vytvořit pokračování, které se spustí, když se dokončí jakákoli skupina úkolů nebo všechny skupiny úkolů. Chcete-li provést pokračování, pokud byly dokončeny všechny předcházející úlohy, můžete volat statickou metodou (Shared
v jazyce Visual Basic) metodu Task.WhenAll nebo metodu instance TaskFactory.ContinueWhenAll. Chcete-li provést pokračování, když je některá z předcházejících úloh dokončena, můžete zavolat statickou metodu (Shared
v jazyce Visual Basic) Task.WhenAny nebo metodu instance TaskFactory.ContinueWhenAny.
Task.WhenAll Volání a Task.WhenAny přetížení neblokují volající vlákno. Obvykle však voláte všechny metody kromě Task.WhenAll(IEnumerable<Task>) a Task.WhenAll(Task[]) metod, abyste načetli vrácenou vlastnost Task<TResult>.Result, která blokuje volající vlákno.
Následující příklad volá metodu Task.WhenAll(IEnumerable<Task>) k vytvoření pokračujícího úkolu, který odráží výsledky jejích 10 antecedentních úkolů. Každý předchozí úkol umocní na druhou hodnotu indexu, která je v rozsahu od jedné do deseti. Pokud se antecedenty úspěšně dokončí (jejich Task.Status vlastnost je TaskStatus.RanToCompletion), vlastnost Task<TResult>.Result pokračování je pole hodnot Task<TResult>.Result, které vrací každý antecedens. Příklad je přidá k výpočtu součtu čtverců pro všechna čísla mezi 1 a 10:
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
public class WhenAllExample
{
public static async Task Main()
{
var tasks = new List<Task<int>>();
for (int ctr = 1; ctr <= 10; ctr++)
{
int baseValue = ctr;
tasks.Add(Task.Factory.StartNew(b => (int)b! * (int)b, baseValue));
}
var results = await Task.WhenAll(tasks);
int sum = 0;
for (int ctr = 0; ctr <= results.Length - 1; ctr++)
{
var result = results[ctr];
Console.Write($"{result} {((ctr == results.Length - 1) ? "=" : "+")} ");
sum += result;
}
Console.WriteLine(sum);
}
}
// The example displays the similar output:
// 1 + 4 + 9 + 16 + 25 + 36 + 49 + 64 + 81 + 100 = 385
Imports System.Collections.Generic
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim tasks As New List(Of Task(Of Integer))()
For ctr As Integer = 1 To 10
Dim baseValue As Integer = ctr
tasks.Add(Task.Factory.StartNew(Function(b)
Dim i As Integer = CInt(b)
Return i * i
End Function, baseValue))
Next
Dim continuation = Task.WhenAll(tasks)
Dim sum As Long = 0
For ctr As Integer = 0 To continuation.Result.Length - 1
Console.Write("{0} {1} ", continuation.Result(ctr),
If(ctr = continuation.Result.Length - 1, "=", "+"))
sum += continuation.Result(ctr)
Next
Console.WriteLine(sum)
End Sub
End Module
' The example displays the following output:
' 1 + 4 + 9 + 16 + 25 + 36 + 49 + 64 + 81 + 100 = 385
Možnosti pokračování
Při vytváření pokračování s jedním úkolem můžete použít ContinueWith přetížení, které přebírá System.Threading.Tasks.TaskContinuationOptions hodnotu výčtu k určení podmínek, za kterých pokračování začíná. Můžete například určit, že pokračování bude spuštěno pouze v případě, že se úspěšně dokončí antecedent, nebo pouze v případě, že se dokončí ve stavu chyby. Pokud podmínka není pravdivá ve chvíli, kdy je antecedent připraven vyvolat pokračování, pokračování okamžitě přejde do stavu TaskStatus.Canceled a nelze ho spustit později.
Mnoho metod pokračování s více úkoly, jako jsou přetížení metody TaskFactory.ContinueWhenAll, také zahrnuje parametr System.Threading.Tasks.TaskContinuationOptions. Platná je však pouze část všech System.Threading.Tasks.TaskContinuationOptions členů výčtu. Můžete zadat System.Threading.Tasks.TaskContinuationOptions hodnoty, které mají protějšky ve výčtu System.Threading.Tasks.TaskCreationOptions , například TaskContinuationOptions.AttachedToParent, TaskContinuationOptions.LongRunninga TaskContinuationOptions.PreferFairness. Pokud zadáte některou z možností NotOn
s pokračováním OnlyOn
pro více úloh, během běhu ArgumentOutOfRangeException bude vyvolána výjimka.
Další informace o možnostech pokračování úkolu najdete v TaskContinuationOptions článku.
Předání dat do pokračování
Metoda Task.ContinueWith předá odkaz na předchůdce jako argument uživatelskému delegátu pokračování. Pokud je předchůdce System.Threading.Tasks.Task<TResult> objektem a úkol běžel, dokud nebyl dokončen, může pokračování přistupovat k Task<TResult>.Result vlastnosti úkolu.
Vlastnost Task<TResult>.Result blokuje, dokud se úkol nedokončil. Pokud však byla úloha zrušena nebo chybová, pokus o přístup k vlastnosti Result vyvolá AggregateException výjimku. Tomuto problému se můžete vyhnout pomocí OnlyOnRanToCompletion možnosti, jak je znázorněno v následujícím příkladu:
using System;
using System.Threading.Tasks;
public class ResultExample
{
public static async Task Main()
{
var task = Task.Run(
() =>
{
DateTime date = DateTime.Now;
return date.Hour > 17
? "evening"
: date.Hour > 12
? "afternoon"
: "morning";
});
await task.ContinueWith(
antecedent =>
{
Console.WriteLine($"Good {antecedent.Result}!");
Console.WriteLine($"And how are you this fine {antecedent.Result}?");
}, TaskContinuationOptions.OnlyOnRanToCompletion);
}
}
// The example displays the similar output:
// Good afternoon!
// And how are you this fine afternoon?
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim t = Task.Run(Function()
Dim dat As DateTime = DateTime.Now
If dat = DateTime.MinValue Then
Throw New ArgumentException("The clock is not working.")
End If
If dat.Hour > 17 Then
Return "evening"
Else If dat.Hour > 12 Then
Return "afternoon"
Else
Return "morning"
End If
End Function)
Dim c = t.ContinueWith(Sub(antecedent)
Console.WriteLine("Good {0}!",
antecedent.Result)
Console.WriteLine("And how are you this fine {0}?",
antecedent.Result)
End Sub, TaskContinuationOptions.OnlyOnRanToCompletion)
c.Wait()
End Sub
End Module
' The example displays output like the following:
' Good afternoon!
' And how are you this fine afternoon?
Pokud chcete, aby pokračování běželo i v případě, že antecedent nedoběhl k úspěšnému dokončení, musíte zachytit výjimku. Jedním z přístupů je otestování Task.Status vlastnosti objektu antecedent a pokus o přístup k Result vlastnosti pouze v případě, že stav není Faulted nebo Canceled. Můžete také prozkoumat vlastnost Exception antecedentu. Další informace naleznete v tématu Zpracování výjimek. Následující příklad upraví předchozí příklad pro přístup k vlastnosti antecedent Task<TResult>.Result pouze v případě, že jeho stav je TaskStatus.RanToCompletion:
using System;
using System.Threading.Tasks;
public class ResultTwoExample
{
public static async Task Main() =>
await Task.Run(
() =>
{
DateTime date = DateTime.Now;
return date.Hour > 17
? "evening"
: date.Hour > 12
? "afternoon"
: "morning";
})
.ContinueWith(
antecedent =>
{
if (antecedent.Status == TaskStatus.RanToCompletion)
{
Console.WriteLine($"Good {antecedent.Result}!");
Console.WriteLine($"And how are you this fine {antecedent.Result}?");
}
else if (antecedent.Status == TaskStatus.Faulted)
{
Console.WriteLine(antecedent.Exception!.GetBaseException().Message);
}
});
}
// The example displays output like the following:
// Good afternoon!
// And how are you this fine afternoon?
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim t = Task.Run(Function()
Dim dat As DateTime = DateTime.Now
If dat = DateTime.MinValue Then
Throw New ArgumentException("The clock is not working.")
End If
If dat.Hour > 17 Then
Return "evening"
Else If dat.Hour > 12 Then
Return "afternoon"
Else
Return "morning"
End If
End Function)
Dim c = t.ContinueWith(Sub(antecedent)
If t.Status = TaskStatus.RanToCompletion Then
Console.WriteLine("Good {0}!",
antecedent.Result)
Console.WriteLine("And how are you this fine {0}?",
antecedent.Result)
Else If t.Status = TaskStatus.Faulted Then
Console.WriteLine(t.Exception.GetBaseException().Message)
End If
End Sub)
End Sub
End Module
' The example displays output like the following:
' Good afternoon!
' And how are you this fine afternoon?
Zrušení pokračování
Vlastnost Task.Status pokračování je nastavena v TaskStatus.Canceled následujících situacích:
OperationCanceledException Vyvolá výjimku v reakci na žádost o zrušení. Stejně jako u jakéhokoli úkolu platí, že pokud výjimka obsahuje stejný token, který byl předán pokračování, považuje se za potvrzení spolupracujícího zrušení.
Pokračování je předáno s System.Threading.CancellationToken, jehož IsCancellationRequested vlastnost je
true
. V tomto případě se pokračování nespustí a přejde do TaskStatus.Canceled stavu.Pokračování se nikdy nespustí, protože podmínka nastavená argumentem TaskContinuationOptions nebyla splněna. Například pokud antecedent přejde do TaskStatus.Faulted stavu, jeho pokračování, které má možnost TaskContinuationOptions.NotOnFaulted, se nespustí, ale přejde do Canceled stavu.
Pokud úkol a jeho pokračování představují dvě části stejné logické operace, můžete předat stejný token zrušení oběma úkolům, jak je znázorněno v následujícím příkladu. Skládá se z antecedentu, který generuje seznam celých čísel dělitelných číslem 33 a ten předává pokračování. Pokračování pak zobrazí seznam. Antecedent i pokračování se pravidelně pozastavují v náhodných intervalech. Kromě toho se objekt System.Threading.Timer používá k provedení Elapsed
metody po pětisekundovém časovém limitu. Tento příklad volá metodu CancellationTokenSource.Cancel , která způsobí, že aktuálně spuštěná úloha volá metodu CancellationToken.ThrowIfCancellationRequested . Zda je metoda CancellationTokenSource.Cancel volána během spuštění antecedentu nebo jeho pokračování, závisí na době trvání náhodně vygenerovaných pauz. Pokud je antecedent zrušen, pokračování nezačne. Pokud se objekt antecedent nezruší, můžete token přesto použít ke zrušení pokračování.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
public class CancellationExample
{
static readonly Random s_random = new Random((int)DateTime.Now.Ticks);
public static async Task Main()
{
using var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
var timer = new Timer(Elapsed, cts, 5000, Timeout.Infinite);
var task = Task.Run(
async () =>
{
var product33 = new List<int>();
for (int index = 1; index < short.MaxValue; index++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("\nCancellation requested in antecedent...\n");
token.ThrowIfCancellationRequested();
}
if (index % 2000 == 0)
{
int delay = s_random.Next(16, 501);
await Task.Delay(delay);
}
if (index % 33 == 0)
{
product33.Add(index);
}
}
return product33.ToArray();
}, token);
Task<double> continuation = task.ContinueWith(
async antecedent =>
{
Console.WriteLine("Multiples of 33:\n");
int[] array = antecedent.Result;
for (int index = 0; index < array.Length; index++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("\nCancellation requested in continuation...\n");
token.ThrowIfCancellationRequested();
}
if (index % 100 == 0)
{
int delay = s_random.Next(16, 251);
await Task.Delay(delay);
}
Console.Write($"{array[index]:N0}{(index != array.Length - 1 ? ", " : "")}");
if (Console.CursorLeft >= 74)
{
Console.WriteLine();
}
}
Console.WriteLine();
return array.Average();
}, token).Unwrap();
try
{
await task;
double result = await continuation;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine($"\nAntecedent Status: {task.Status}");
Console.WriteLine($"Continuation Status: {continuation.Status}");
}
static void Elapsed(object? state)
{
if (state is CancellationTokenSource cts)
{
cts.Cancel();
Console.WriteLine("\nCancellation request issued...\n");
}
}
}
// The example displays the similar output:
// Multiples of 33:
//
// 33, 66, 99, 132, 165, 198, 231, 264, 297, 330, 363, 396, 429, 462, 495, 528,
// 561, 594, 627, 660, 693, 726, 759, 792, 825, 858, 891, 924, 957, 990, 1,023,
// 1,056, 1,089, 1,122, 1,155, 1,188, 1,221, 1,254, 1,287, 1,320, 1,353, 1,386,
// 1,419, 1,452, 1,485, 1,518, 1,551, 1,584, 1,617, 1,650, 1,683, 1,716, 1,749,
// 1,782, 1,815, 1,848, 1,881, 1,914, 1,947, 1,980, 2,013, 2,046, 2,079, 2,112,
// 2,145, 2,178, 2,211, 2,244, 2,277, 2,310, 2,343, 2,376, 2,409, 2,442, 2,475,
// 2,508, 2,541, 2,574, 2,607, 2,640, 2,673, 2,706, 2,739, 2,772, 2,805, 2,838,
// 2,871, 2,904, 2,937, 2,970, 3,003, 3,036, 3,069, 3,102, 3,135, 3,168, 3,201,
// 3,234, 3,267, 3,300, 3,333, 3,366, 3,399, 3,432, 3,465, 3,498, 3,531, 3,564,
// 3,597, 3,630, 3,663, 3,696, 3,729, 3,762, 3,795, 3,828, 3,861, 3,894, 3,927,
// 3,960, 3,993, 4,026, 4,059, 4,092, 4,125, 4,158, 4,191, 4,224, 4,257, 4,290,
// 4,323, 4,356, 4,389, 4,422, 4,455, 4,488, 4,521, 4,554, 4,587, 4,620, 4,653,
// 4,686, 4,719, 4,752, 4,785, 4,818, 4,851, 4,884, 4,917, 4,950, 4,983, 5,016,
// 5,049, 5,082, 5,115, 5,148, 5,181, 5,214, 5,247, 5,280, 5,313, 5,346, 5,379,
// 5,412, 5,445, 5,478, 5,511, 5,544, 5,577, 5,610, 5,643, 5,676, 5,709, 5,742,
// Cancellation request issued...
//
// 5,775,
// Cancellation requested in continuation...
//
// The operation was canceled.
//
// Antecedent Status: RanToCompletion
// Continuation Status: Canceled
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim rnd As New Random()
Dim lockObj As New Object()
Dim cts As New CancellationTokenSource()
Dim token As CancellationToken = cts.Token
Dim timer As New Timer(AddressOf Elapsed, cts, 5000, Timeout.Infinite)
Dim t = Task.Run(Function()
Dim product33 As New List(Of Integer)()
For ctr As Integer = 1 To Int16.MaxValue
' Check for cancellation.
If token.IsCancellationRequested Then
Console.WriteLine("\nCancellation requested in antecedent...\n")
token.ThrowIfCancellationRequested()
End If
' Introduce a delay.
If ctr Mod 2000 = 0 Then
Dim delay As Integer
SyncLock lockObj
delay = rnd.Next(16, 501)
End SyncLock
Thread.Sleep(delay)
End If
' Determine if this is a multiple of 33.
If ctr Mod 33 = 0 Then product33.Add(ctr)
Next
Return product33.ToArray()
End Function, token)
Dim continuation = t.ContinueWith(Sub(antecedent)
Console.WriteLine("Multiples of 33:" + vbCrLf)
Dim arr = antecedent.Result
For ctr As Integer = 0 To arr.Length - 1
If token.IsCancellationRequested Then
Console.WriteLine("{0}Cancellation requested in continuation...{0}",
vbCrLf)
token.ThrowIfCancellationRequested()
End If
If ctr Mod 100 = 0 Then
Dim delay As Integer
SyncLock lockObj
delay = rnd.Next(16, 251)
End SyncLock
Thread.Sleep(delay)
End If
Console.Write("{0:N0}{1}", arr(ctr),
If(ctr <> arr.Length - 1, ", ", ""))
If Console.CursorLeft >= 74 Then Console.WriteLine()
Next
Console.WriteLine()
End Sub, token)
Try
continuation.Wait()
Catch e As AggregateException
For Each ie In e.InnerExceptions
Console.WriteLine("{0}: {1}", ie.GetType().Name,
ie.Message)
Next
Finally
cts.Dispose()
End Try
Console.WriteLine(vbCrLf + "Antecedent Status: {0}", t.Status)
Console.WriteLine("Continuation Status: {0}", continuation.Status)
End Sub
Private Sub Elapsed(state As Object)
Dim cts As CancellationTokenSource = TryCast(state, CancellationTokenSource)
If cts Is Nothing Then return
cts.Cancel()
Console.WriteLine("{0}Cancellation request issued...{0}", vbCrLf)
End Sub
End Module
' The example displays output like the following:
' Multiples of 33:
'
' 33, 66, 99, 132, 165, 198, 231, 264, 297, 330, 363, 396, 429, 462, 495, 528,
' 561, 594, 627, 660, 693, 726, 759, 792, 825, 858, 891, 924, 957, 990, 1,023,
' 1,056, 1,089, 1,122, 1,155, 1,188, 1,221, 1,254, 1,287, 1,320, 1,353, 1,386,
' 1,419, 1,452, 1,485, 1,518, 1,551, 1,584, 1,617, 1,650, 1,683, 1,716, 1,749,
' 1,782, 1,815, 1,848, 1,881, 1,914, 1,947, 1,980, 2,013, 2,046, 2,079, 2,112,
' 2,145, 2,178, 2,211, 2,244, 2,277, 2,310, 2,343, 2,376, 2,409, 2,442, 2,475,
' 2,508, 2,541, 2,574, 2,607, 2,640, 2,673, 2,706, 2,739, 2,772, 2,805, 2,838,
' 2,871, 2,904, 2,937, 2,970, 3,003, 3,036, 3,069, 3,102, 3,135, 3,168, 3,201,
' 3,234, 3,267, 3,300, 3,333, 3,366, 3,399, 3,432, 3,465, 3,498, 3,531, 3,564,
' 3,597, 3,630, 3,663, 3,696, 3,729, 3,762, 3,795, 3,828, 3,861, 3,894, 3,927,
' 3,960, 3,993, 4,026, 4,059, 4,092, 4,125, 4,158, 4,191, 4,224, 4,257, 4,290,
' 4,323, 4,356, 4,389, 4,422, 4,455, 4,488, 4,521, 4,554, 4,587, 4,620, 4,653,
' 4,686, 4,719, 4,752, 4,785, 4,818, 4,851, 4,884, 4,917, 4,950, 4,983, 5,016,
' 5,049, 5,082, 5,115, 5,148, 5,181, 5,214, 5,247, 5,280, 5,313, 5,346, 5,379,
' 5,412, 5,445, 5,478, 5,511, 5,544, 5,577, 5,610, 5,643, 5,676, 5,709, 5,742,
' 5,775, 5,808, 5,841, 5,874, 5,907, 5,940, 5,973, 6,006, 6,039, 6,072, 6,105,
' 6,138, 6,171, 6,204, 6,237, 6,270, 6,303, 6,336, 6,369, 6,402, 6,435, 6,468,
' 6,501, 6,534, 6,567, 6,600, 6,633, 6,666, 6,699, 6,732, 6,765, 6,798, 6,831,
' 6,864, 6,897, 6,930, 6,963, 6,996, 7,029, 7,062, 7,095, 7,128, 7,161, 7,194,
' 7,227, 7,260, 7,293, 7,326, 7,359, 7,392, 7,425, 7,458, 7,491, 7,524, 7,557,
' 7,590, 7,623, 7,656, 7,689, 7,722, 7,755, 7,788, 7,821, 7,854, 7,887, 7,920,
' 7,953, 7,986, 8,019, 8,052, 8,085, 8,118, 8,151, 8,184, 8,217, 8,250, 8,283,
' 8,316, 8,349, 8,382, 8,415, 8,448, 8,481, 8,514, 8,547, 8,580, 8,613, 8,646,
' 8,679, 8,712, 8,745, 8,778, 8,811, 8,844, 8,877, 8,910, 8,943, 8,976, 9,009,
' 9,042, 9,075, 9,108, 9,141, 9,174, 9,207, 9,240, 9,273, 9,306, 9,339, 9,372,
' 9,405, 9,438, 9,471, 9,504, 9,537, 9,570, 9,603, 9,636, 9,669, 9,702, 9,735,
' 9,768, 9,801, 9,834, 9,867, 9,900, 9,933, 9,966, 9,999, 10,032, 10,065, 10,098,
' 10,131, 10,164, 10,197, 10,230, 10,263, 10,296, 10,329, 10,362, 10,395, 10,428,
' 10,461, 10,494, 10,527, 10,560, 10,593, 10,626, 10,659, 10,692, 10,725, 10,758,
' 10,791, 10,824, 10,857, 10,890, 10,923, 10,956, 10,989, 11,022, 11,055, 11,088,
' 11,121, 11,154, 11,187, 11,220, 11,253, 11,286, 11,319, 11,352, 11,385, 11,418,
' 11,451, 11,484, 11,517, 11,550, 11,583, 11,616, 11,649, 11,682, 11,715, 11,748,
' 11,781, 11,814, 11,847, 11,880, 11,913, 11,946, 11,979, 12,012, 12,045, 12,078,
' 12,111, 12,144, 12,177, 12,210, 12,243, 12,276, 12,309, 12,342, 12,375, 12,408,
' 12,441, 12,474, 12,507, 12,540, 12,573, 12,606, 12,639, 12,672, 12,705, 12,738,
' 12,771, 12,804, 12,837, 12,870, 12,903, 12,936, 12,969, 13,002, 13,035, 13,068,
' 13,101, 13,134, 13,167, 13,200, 13,233, 13,266,
' Cancellation requested in continuation...
'
'
' Cancellation request issued...
'
' TaskCanceledException: A task was canceled.
'
' Antecedent Status: RanToCompletion
' Continuation Status: Canceled
Můžete také zabránit provedení pokračování, pokud jeho antecedent je zrušen bez toho, aby pokračování mělo token zrušení. Zadejte token pomocí volby TaskContinuationOptions.NotOnCanceled při vytváření pokračování, jak je uvedeno v následujícím příkladu:
using System;
using System.Threading;
using System.Threading.Tasks;
public class CancellationTwoExample
{
public static async Task Main()
{
using var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.Cancel();
var task = Task.FromCanceled(token);
Task continuation =
task.ContinueWith(
antecedent => Console.WriteLine("The continuation is running."),
TaskContinuationOptions.NotOnCanceled);
try
{
await task;
}
catch (Exception ex)
{
Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
Console.WriteLine();
}
Console.WriteLine($"Task {task.Id}: {task.Status:G}");
Console.WriteLine($"Task {continuation.Id}: {continuation.Status:G}");
}
}
// The example displays the similar output:
// TaskCanceledException: A task was canceled.
//
// Task 1: Canceled
// Task 2: Canceled
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim cts As New CancellationTokenSource()
Dim token As CancellationToken = cts.Token
cts.Cancel()
Dim t As Task = Task.FromCanceled(token)
Dim continuation As Task = t.ContinueWith(Sub(antecedent)
Console.WriteLine("The continuation is running.")
End Sub, TaskContinuationOptions.NotOnCanceled)
Try
t.Wait()
Catch e As AggregateException
For Each ie In e.InnerExceptions
Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
Next
Console.WriteLine()
Finally
cts.Dispose()
End Try
Console.WriteLine("Task {0}: {1:G}", t.Id, t.Status)
Console.WriteLine("Task {0}: {1:G}", continuation.Id,
continuation.Status)
End Sub
End Module
' The example displays the following output:
' TaskCanceledException: A task was canceled.
'
' Task 1: Canceled
' Task 2: Canceled
Poté, co pokračování přejde do Canceled stavu, může ovlivnit pokračování, která následují, v závislosti na TaskContinuationOptions, které jsou zadány pro tato pokračování.
Pokračování, která jsou odstraněna, se nespustí.
Pokračování a podřízené úkoly
Pokračování se nespustí, dokud nebude dokončen předcházející úkol a všechny jeho připojené podřízené úkoly. Pokračování nečeká na dokončení oddělených dceřiných úkolů. Následující dva příklady znázorňují podřízené úlohy, které se připojují a odpojují od antecedentu, jenž vytváří pokračování. V následujícím příkladu se pokračování spustí až po dokončení všech podřízených úloh a vícenásobná spuštění tohoto příkladu vždy vytvoří stejný výstup. Příklad spustí antecedent voláním metody TaskFactory.StartNew, protože metoda Task.Run ve výchozím nastavení vytvoří nadřazenou úlohu, jejíž výchozí možností vytvoření úlohy je TaskCreationOptions.DenyChildAttach.
using System;
using System.Threading.Tasks;
public class AttachedExample
{
public static async Task Main()
{
await Task.Factory
.StartNew(
() =>
{
Console.WriteLine($"Running antecedent task {Task.CurrentId}...");
Console.WriteLine("Launching attached child tasks...");
for (int ctr = 1; ctr <= 5; ctr++)
{
int index = ctr;
Task.Factory.StartNew(async value =>
{
Console.WriteLine($" Attached child task #{value} running");
await Task.Delay(1000);
}, index, TaskCreationOptions.AttachedToParent);
}
Console.WriteLine("Finished launching attached child tasks...");
}).ContinueWith(
antecedent =>
Console.WriteLine($"Executing continuation of Task {antecedent.Id}"));
}
}
// The example displays the similar output:
// Running antecedent task 1...
// Launching attached child tasks...
// Finished launching attached child tasks...
// Attached child task #1 running
// Attached child task #5 running
// Attached child task #3 running
// Attached child task #2 running
// Attached child task #4 running
// Executing continuation of Task 1
Imports System.Threading
Imports System.Threading.Tasks
Public Module Example
Public Sub Main()
Dim t = Task.Factory.StartNew(Sub()
Console.WriteLine("Running antecedent task {0}...",
Task.CurrentId)
Console.WriteLine("Launching attached child tasks...")
For ctr As Integer = 1 To 5
Dim index As Integer = ctr
Task.Factory.StartNew(Sub(value)
Console.WriteLine(" Attached child task #{0} running",
value)
Thread.Sleep(1000)
End Sub, index, TaskCreationOptions.AttachedToParent)
Next
Console.WriteLine("Finished launching attached child tasks...")
End Sub)
Dim continuation = t.ContinueWith(Sub(antecedent)
Console.WriteLine("Executing continuation of Task {0}",
antecedent.Id)
End Sub)
continuation.Wait()
End Sub
End Module
' The example displays the following output:
' Running antecedent task 1...
' Launching attached child tasks...
' Finished launching attached child tasks...
' Attached child task #5 running
' Attached child task #1 running
' Attached child task #2 running
' Attached child task #3 running
' Attached child task #4 running
' Executing continuation of Task 1
Pokud jsou však podřízené úkoly odděleny od předchůdce, poběží pokračování ihned po ukončení předchůdce, bez ohledu na stav podřízených úkolů. V důsledku toho může několik spuštění následujícího příkladu vytvořit proměnlivý výstup, který závisí na tom, jak plánovač úloh zpracoval každou podřízenou úlohu.
using System;
using System.Threading.Tasks;
public class DetachedExample
{
public static async Task Main()
{
Task task =
Task.Factory.StartNew(
() =>
{
Console.WriteLine($"Running antecedent task {Task.CurrentId}...");
Console.WriteLine("Launching attached child tasks...");
for (int ctr = 1; ctr <= 5; ctr++)
{
int index = ctr;
Task.Factory.StartNew(
async value =>
{
Console.WriteLine($" Attached child task #{value} running");
await Task.Delay(1000);
}, index);
}
Console.WriteLine("Finished launching detached child tasks...");
}, TaskCreationOptions.DenyChildAttach);
Task continuation =
task.ContinueWith(
antecedent =>
Console.WriteLine($"Executing continuation of Task {antecedent.Id}"));
await continuation;
Console.ReadLine();
}
}
// The example displays the similar output:
// Running antecedent task 1...
// Launching attached child tasks...
// Finished launching detached child tasks...
// Executing continuation of Task 1
// Attached child task #1 running
// Attached child task #5 running
// Attached child task #2 running
// Attached child task #3 running
// Attached child task #4 running
Imports System.Threading
Imports System.Threading.Tasks
Public Module Example
Public Sub Main()
Dim t = Task.Factory.StartNew(Sub()
Console.WriteLine("Running antecedent task {0}...",
Task.CurrentId)
Console.WriteLine("Launching attached child tasks...")
For ctr As Integer = 1 To 5
Dim index As Integer = ctr
Task.Factory.StartNew(Sub(value)
Console.WriteLine(" Attached child task #{0} running",
value)
Thread.Sleep(1000)
End Sub, index)
Next
Console.WriteLine("Finished launching detached child tasks...")
End Sub, TaskCreationOptions.DenyChildAttach)
Dim continuation = t.ContinueWith(Sub(antecedent)
Console.WriteLine("Executing continuation of Task {0}",
antecedent.Id)
End Sub)
continuation.Wait()
End Sub
End Module
' The example displays output like the following:
' Running antecedent task 1...
' Launching attached child tasks...
' Finished launching detached child tasks...
' Attached child task #1 running
' Attached child task #2 running
' Attached child task #5 running
' Attached child task #3 running
' Executing continuation of Task 1
' Attached child task #4 running
Konečný stav předcházejícího úkolu závisí na konečném stavu jakýchkoli připojených podřízených úkolů. Stav odpojených dětských úkolů nemá vliv na nadřazený úkol. Další informace naleznete v tématu Připojené a odpojené podřízené úkoly.
Přiřaďte stav pomocí pokračování
K pokračování úkolu můžete přidružit libovolný stav. Metoda ContinueWith poskytuje přetížené verze, z nichž každá přijímá hodnotu Object, která představuje stav pokračování. Později můžete k tomuto objektu Task.AsyncState stavu přistupovat pomocí vlastnosti. Tento objekt stavu je null
, pokud nezadáte hodnotu.
Stav pokračování je užitečný při převodu existujícího kódu, který používá asynchronní programovací model (APM) k použití TPL. V APM můžete zadat stav objektu v metodě BeginMetoda a později můžete použít IAsyncResult.AsyncState vlastnost pro přístup k ho stavu. Chcete-li zachovat tento stav při převodu kódu, který používá APM k použití TPL, použijte metodu ContinueWith .
Stav pokračování může být užitečný také při práci s Task objekty v ladicím programu Visual Studio. Například v okně Paralelní úkoly se ve sloupci Task zobrazí řetězcová reprezentace stavového objektu pro každý úkol. Další informace o okně Paralelní úlohy naleznete v části Použití okna Úkoly.
Následující příklad ukazuje, jak použít stav pokračování. Vytvoří řetězec pokračujících úloh. Každý úkol poskytuje aktuální čas a DateTime objekt pro state
parametr metody ContinueWith. Každý DateTime objekt představuje čas vytvoření úlohy pokračování. Každý úkol je výsledkem druhého DateTime objektu, který představuje čas dokončení úkolu. Po dokončení všech úkolů se v tomto příkladu zobrazí čas vytvoření a čas, kdy každý pokračovací úkol skončí.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
class ContinuationStateExample
{
static DateTime DoWork()
{
Thread.Sleep(2000);
return DateTime.Now;
}
static async Task Main()
{
Task<DateTime> task = Task.Run(() => DoWork());
var continuations = new List<Task<DateTime>>();
for (int i = 0; i < 5; i++)
{
task = task.ContinueWith((antecedent, _) => DoWork(), DateTime.Now);
continuations.Add(task);
}
await task;
foreach (Task<DateTime> continuation in continuations)
{
DateTime start = (DateTime)continuation.AsyncState!;
DateTime end = continuation.Result;
Console.WriteLine($"Task was created at {start.TimeOfDay} and finished at {end.TimeOfDay}.");
}
Console.ReadLine();
}
}
// The example displays the similar output:
// Task was created at 10:56:21.1561762 and finished at 10:56:25.1672062.
// Task was created at 10:56:21.1610677 and finished at 10:56:27.1707646.
// Task was created at 10:56:21.1610677 and finished at 10:56:29.1743230.
// Task was created at 10:56:21.1610677 and finished at 10:56:31.1779883.
// Task was created at 10:56:21.1610677 and finished at 10:56:33.1837083.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
' Demonstrates how to associate state with task continuations.
Public Module ContinuationState
' Simulates a lengthy operation and returns the time at which
' the operation completed.
Public Function DoWork() As Date
' Simulate work by suspending the current thread
' for two seconds.
Thread.Sleep(2000)
' Return the current time.
Return Date.Now
End Function
Public Sub Main()
' Start a root task that performs work.
Dim t As Task(Of Date) = Task(Of Date).Run(Function() DoWork())
' Create a chain of continuation tasks, where each task is
' followed by another task that performs work.
Dim continuations As New List(Of Task(Of DateTime))()
For i As Integer = 0 To 4
' Provide the current time as the state of the continuation.
t = t.ContinueWith(Function(antecedent, state) DoWork(), DateTime.Now)
continuations.Add(t)
Next
' Wait for the last task in the chain to complete.
t.Wait()
' Display the creation time of each continuation (the state object)
' and the completion time (the result of that task) to the console.
For Each continuation In continuations
Dim start As DateTime = CDate(continuation.AsyncState)
Dim [end] As DateTime = continuation.Result
Console.WriteLine("Task was created at {0} and finished at {1}.",
start.TimeOfDay, [end].TimeOfDay)
Next
End Sub
End Module
' The example displays output like the following:
' Task was created at 10:56:21.1561762 and finished at 10:56:25.1672062.
' Task was created at 10:56:21.1610677 and finished at 10:56:27.1707646.
' Task was created at 10:56:21.1610677 and finished at 10:56:29.1743230.
' Task was created at 10:56:21.1610677 and finished at 10:56:31.1779883.
' Task was created at 10:56:21.1610677 and finished at 10:56:33.1837083.
Pokračování, která vracejí typy úloh
Někdy může být potřeba zřetězit pokračování, které vrací Task typ. Tyto úkoly se označují jako vnořené úkoly. Když nadřazený úkol volá Task<TResult>.ContinueWith a poskytuje continuationFunction
úkol, který vrací úkol, můžete zavolat k Unwrap pro vytvoření proxy úlohy, která představuje asynchronní operaci <Task<Task<T>>>
nebo Task(Of Task(Of T))
(Visual Basic).
Následující příklad ukazuje, jak používat pokračování, která obalí další funkce vracející úlohy. Každé pokračování lze rozbalit a odhalit vnitřní úkol, který byl zabalený.
using System;
using System.Threading;
using System.Threading.Tasks;
public class UnwrapExample
{
public static async Task Main()
{
Task<int> taskOne = RemoteIncrement(0);
Console.WriteLine("Started RemoteIncrement(0)");
Task<int> taskTwo = RemoteIncrement(4)
.ContinueWith(t => RemoteIncrement(t.Result))
.Unwrap().ContinueWith(t => RemoteIncrement(t.Result))
.Unwrap().ContinueWith(t => RemoteIncrement(t.Result))
.Unwrap();
Console.WriteLine("Started RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)");
try
{
await taskOne;
Console.WriteLine("Finished RemoteIncrement(0)");
await taskTwo;
Console.WriteLine("Finished RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)");
}
catch (Exception e)
{
Console.WriteLine($"A task has thrown the following (unexpected) exception:\n{e}");
}
}
static Task<int> RemoteIncrement(int number) =>
Task<int>.Factory.StartNew(
obj =>
{
Thread.Sleep(1000);
int x = (int)(obj!);
Console.WriteLine("Thread={0}, Next={1}", Thread.CurrentThread.ManagedThreadId, ++x);
return x;
},
number);
}
// The example displays the similar output:
// Started RemoteIncrement(0)
// Started RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)
// Thread=4, Next=1
// Finished RemoteIncrement(0)
// Thread=5, Next=5
// Thread=6, Next=6
// Thread=6, Next=7
// Thread=6, Next=8
// Finished RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)
Imports System.Threading
Module UnwrapExample
Sub Main()
Dim taskOne As Task(Of Integer) = RemoteIncrement(0)
Console.WriteLine("Started RemoteIncrement(0)")
Dim taskTwo As Task(Of Integer) = RemoteIncrement(4).
ContinueWith(Function(t) RemoteIncrement(t.Result)).
Unwrap().ContinueWith(Function(t) RemoteIncrement(t.Result)).
Unwrap().ContinueWith(Function(t) RemoteIncrement(t.Result)).
Unwrap()
Console.WriteLine("Started RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)")
Try
taskOne.Wait()
Console.WriteLine("Finished RemoteIncrement(0)")
taskTwo.Wait()
Console.WriteLine("Finished RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)")
Catch e As AggregateException
Console.WriteLine($"A task has thrown the following (unexpected) exception:{vbLf}{e}")
End Try
End Sub
Function RemoteIncrement(ByVal number As Integer) As Task(Of Integer)
Return Task(Of Integer).Factory.StartNew(
Function(obj)
Thread.Sleep(1000)
Dim x As Integer = CInt(obj)
Console.WriteLine("Thread={0}, Next={1}", Thread.CurrentThread.ManagedThreadId, Interlocked.Increment(x))
Return x
End Function, number)
End Function
End Module
' The example displays the similar output:
' Started RemoteIncrement(0)
' Started RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)
' Thread=4, Next=1
' Finished RemoteIncrement(0)
' Thread=5, Next=5
' Thread=6, Next=6
' Thread=6, Next=7
' Thread=6, Next=8
' Finished RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)
Další informace o použití Unwrapnaleznete v tématu Postupy: Rozbalení vnořené úlohy.
Zpracování výjimek vyvolaných pokračováními
Vztah antecedentu a pokračování není vztah rodič-dítě. Výjimky vyvolané pokračováním se do antecedentu nerozšírují. Proto výjimek vyvolaných pokračováním zpracovávejte stejně, jako byste je zpracovali v jakékoli jiné úloze, následujícím způsobem:
- K čekání na pokračování můžete použít metodu Wait, WaitAllnebo WaitAny metodu nebo její obecný protějšek. Můžete počkat na antecedent a jeho pokračování ve stejném
try
příkazu, jak je uvedeno v následujícím příkladu:
using System;
using System.Threading.Tasks;
public class ExceptionExample
{
public static async Task Main()
{
Task<int> task = Task.Run(
() =>
{
Console.WriteLine($"Executing task {Task.CurrentId}");
return 54;
});
var continuation = task.ContinueWith(
antecedent =>
{
Console.WriteLine($"Executing continuation task {Task.CurrentId}");
Console.WriteLine($"Value from antecedent: {antecedent.Result}");
throw new InvalidOperationException();
});
try
{
await task;
await continuation;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
// The example displays the similar output:
// Executing task 1
// Executing continuation task 2
// Value from antecedent: 54
// Operation is not valid due to the current state of the object.
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim task1 = Task(Of Integer).Run(Function()
Console.WriteLine("Executing task {0}",
Task.CurrentId)
Return 54
End Function)
Dim continuation = task1.ContinueWith(Sub(antecedent)
Console.WriteLine("Executing continuation task {0}",
Task.CurrentId)
Console.WriteLine("Value from antecedent: {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
End Sub
End Module
' The example displays the following output:
' Executing task 1
' Executing continuation task 2
' Value from antecedent: 54
' Operation is not valid due to the current state of the object.
- Druhé pokračování můžete použít k pozorování vlastnosti Exception prvního pokračování. V následujícím příkladu se úkol pokusí přečíst z neexistujícího souboru. Pokračování pak zobrazí informace o výjimce v předcházející úloze.
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
public class ExceptionTwoExample
{
public static async Task Main()
{
var task = Task.Run(
() =>
{
string fileText = File.ReadAllText(@"C:\NonexistentFile.txt");
return fileText;
});
Task continuation = task.ContinueWith(
antecedent =>
{
var fileNotFound =
antecedent.Exception
?.InnerExceptions
?.FirstOrDefault(e => e is FileNotFoundException) as FileNotFoundException;
if (fileNotFound != null)
{
Console.WriteLine(fileNotFound.Message);
}
}, TaskContinuationOptions.OnlyOnFaulted);
await continuation;
Console.ReadLine();
}
}
// The example displays the following output:
// Could not find file 'C:\NonexistentFile.txt'.
Imports System.IO
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim t = Task.Run(Function()
Dim s As String = File.ReadAllText("C:\NonexistentFile.txt")
Return s
End Function)
Dim c = t.ContinueWith(Sub(antecedent)
' Get the antecedent's exception information.
For Each ex In antecedent.Exception.InnerExceptions
If TypeOf ex Is FileNotFoundException
Console.WriteLine(ex.Message)
End If
Next
End Sub, TaskContinuationOptions.OnlyOnFaulted)
c.Wait()
End Sub
End Module
' The example displays the following output:
' Could not find file 'C:\NonexistentFile.txt'.
Vzhledem k tomu, že byla spuštěna s možností TaskContinuationOptions.OnlyOnFaulted, pokračování se aktivuje pouze, pokud dojde k výjimce v předcházejícím kroku. Proto lze předpokládat, že vlastnost antecedent Exception není null
. Pokud se pokračování provede bez ohledu na to, zda je v antecedentu vyvolána výjimka, musí před pokusem o zpracování výjimky zkontrolovat, že vlastnost Exception antecedentu není null
, jak ukazuje následující fragment kódu.
var fileNotFound =
antecedent.Exception
?.InnerExceptions
?.FirstOrDefault(e => e is FileNotFoundException) as FileNotFoundException;
if (fileNotFound != null)
{
Console.WriteLine(fileNotFound.Message);
}
' Determine whether an exception occurred.
If antecedent.Exception IsNot Nothing Then
' Get the antecedent's exception information.
For Each ex In antecedent.Exception.InnerExceptions
If TypeOf ex Is FileNotFoundException
Console.WriteLine(ex.Message)
End If
Next
End If
Další informace naleznete v tématu Zpracování výjimek.
- Pokud je pokračování přidruženou podřízenou úlohou vytvořenou pomocí možnosti TaskContinuationOptions.AttachedToParent, její výjimky budou předány nadřazenou úlohou zpět do volajícího vlákna, jako je tomu u jakékoli přidružené podřízené úlohy. Další informace naleznete v tématu Připojené a odpojené podřízené úkoly.