Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Az aszinkron programozásban gyakori, hogy egy aszinkron művelet egy második műveletet hív meg a befejezéskor. A folytatások lehetővé teszik a leszármazott műveletek számára az első művelet eredményeinek felhasználását. A folytatások hagyományosan visszahívási módszerekkel történtek. A párhuzamos feladattárban (TPL) ugyanezt a funkciót a folytatási tevékenységek biztosítják. A folytatási tevékenység (más néven folytatás) egy aszinkron tevékenység, amelyet egy másik tevékenység, más néven az előzmény hív meg, amikor az előzmény befejeződik.
A folytatások viszonylag könnyen használhatók, mégis hatékonyak és rugalmasak. Például megteheted a következőket:
- Adatok továbbítása az előzményből a folytatásba.
- Adja meg a folytatás meghívásának vagy nem meghívásának pontos feltételeit.
- A folytatást akár a kezdés előtt, akár együttműködéssel megszakíthatja a futás közben.
- Adjon meg tippeket a folytatás ütemezéséről.
- Több folytatást hívni ugyanabból az előzményből.
- Egyetlen folytatás meghívása, ha a több előzmény mindegyike vagy bármelyike befejeződött.
- A lánc egymás után folytatódik tetszőleges hosszúságúra.
- Ügyeljen arra, hogy az előzmény által kiváltott kivételeket a megfelelő folytatással kezelje.
Tudnivalók a folytatásokról
A folytatás egy olyan feladat, amely a WaitingForActivation állapotban kerül létrehozásra. Automatikusan aktiválódik, amikor az előzményfeladat vagy a tevékenységek befejeződnek. A Task.Start felhasználói kódban lévő folytatás meghívása kivételt System.InvalidOperationException eredményez.
A folytatás maga is egy Task , és nem blokkolja azt a szálat, amelyen elindult. Hívja meg a Task.Wait metódust, hogy várakozzon, amíg a folytatási feladat befejeződik.
Folytatás létrehozása egyetlen előzményhez
A metódus meghívásával Task.ContinueWith olyan folytatást hozhat létre, amely akkor fut, ha az előzménye befejeződött. Az alábbi példa az alapszintű mintát mutatja be (az egyértelműség kedvéért a kivételkezelés nincs megadva). Végrehajt egy előzményfeladatot taskA , amely egy DayOfWeek olyan objektumot ad vissza, amely a hét aktuális napjának nevét jelzi. Ha taskA befejeződött, a antecedent a folytatási metódusban mutatja az eredményeket ContinueWith. Az előzményfeladat eredményét a rendszer a konzolra írja.
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.
Folytatás létrehozása több előzményhez
Létrehozhat egy folytatást is, amely akkor fut, ha egy tevékenységcsoport bármelyike vagy egésze befejeződött. A folytatás végrehajtásához, ha az összes előzményfeladat befejeződött, meghívhatja a statikus (Shared Visual Basic) Task.WhenAll metódust vagy a példánymetódust TaskFactory.ContinueWhenAll . Ha az előzményfeladatok bármelyikének befejezése után szeretne folytatást végrehajtani, meghívhatja a statikus (Shared Visual Basic) Task.WhenAny metódust vagy a példánymetódust TaskFactory.ContinueWhenAny .
A Task.WhenAll és Task.WhenAny függvény túlterhelések hívásai nem blokkolják a hívó szálat. Általában mégis a Task.WhenAll(IEnumerable<Task>) és Task.WhenAll(Task[]) metódusok kivételével az összes többi metódust meghívja, hogy lekérje a visszaadott Task<TResult>.Result tulajdonságot, ami blokkolja a hívó szálat.
Az alábbi példa meghívja a Task.WhenAll(IEnumerable<Task>) metódust, hogy hozzon létre egy folytatási feladatot, amely tükrözi a 10 előzményfeladat eredményeit. Minden előzménytevékenység egy indexértéket négyzetesen jelöl, amely egytől 10-ig terjed. Ha az előzmények sikeresen befejeződnek (a Task.Status tulajdonságuk TaskStatus.RanToCompletion), a folytatás Task<TResult>.Result tulajdonsága az egyes előzmények által visszaadott Task<TResult>.Result értékek tömbje. A példa hozzáadja őket az 1 és 10 közötti számok négyzetösszegének kiszámításához:
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
Folytatási lehetőségek
Egyfeladatos folytatás létrehozásakor használhatsz egy olyan túlterhelést ContinueWith, amely enumerálási értéket használ System.Threading.Tasks.TaskContinuationOptions a folytatás indításának feltételeinek megadásához. Megadhatja például, hogy a folytatás csak akkor fusson, ha az előzmény sikeresen befejeződött, vagy csak akkor, ha az hibás állapotban fejeződik be. Ha a feltétel nem igaz, ha az előzmény készen áll a folytatás meghívására, a folytatás közvetlenül az TaskStatus.Canceled állapotra vált, és később nem indítható el.
Számos többfeladatos folytatási módszer, például a TaskFactory.ContinueWhenAll metódus túlterhelése is tartalmaz paramétert System.Threading.Tasks.TaskContinuationOptions . Az enumerálási System.Threading.Tasks.TaskContinuationOptions tagoknak azonban csak egy részhalmaza érvényes. Megadhatja azokat az System.Threading.Tasks.TaskContinuationOptions értékeket, amelyek az enumerálásban megfelelőkkel rendelkeznek, például System.Threading.Tasks.TaskCreationOptions, TaskContinuationOptions.AttachedToParent és TaskContinuationOptions.LongRunning. Ha a többfeladatos folytatással bármelyik NotOn vagy OnlyOn lehetőséget megadja, futásidőben ArgumentOutOfRangeException kivétel dobásra kerül.
A tevékenységek folytatásának lehetőségeiről a cikkben talál TaskContinuationOptions további információt.
Adatok továbbítása folytatáshoz
A Task.ContinueWith metódus argumentumként átadja az előzményre mutató hivatkozást a folytatás felhasználói meghatalmazottjának. Ha az előzmény egy System.Threading.Tasks.Task<TResult> objektum, és a tevékenység addig futott, amíg be nem fejeződött, a folytatás hozzáférhet a Task<TResult>.Result tevékenység tulajdonságához.
A Task<TResult>.Result tulajdonság a tevékenység befejezéséig blokkol. Ha azonban a feladat megszakadt vagy hibás volt, a Result tulajdonság elérésének megkísérlése kivételt AggregateException eredményez. Ezt a problémát a következő példában látható módon elkerülheti a OnlyOnRanToCompletion beállítással:
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?
Ha azt szeretné, hogy a folytatás akkor is lefusson, amikor az előzmény nem ért sikeres befejezéshez, védekeznie kell a kivétel ellen. Az egyik módszer az Task.Status előzmény tulajdonságának tesztelése, és csak akkor próbálja meg elérni a Result tulajdonságot, ha az állapot nem Faulted vagy Canceled. Az előzmény Exception tulajdonságát is megvizsgálhatja. További információ: Kivételkezelés. Az alábbi példa módosítja az előző példát, hogy hozzáférjen az előd Task<TResult>.Result tulajdonságához csak akkor, ha annak állapota 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?
Folytatás megszakítása
A Task.Status folytatás tulajdonsága a következő helyzetekben van beállítva TaskStatus.Canceled :
Kivételt OperationCanceledException jelez egy lemondási kérelemre válaszul. Mint minden feladatnál, ha a kivétel ugyanazt a jogkivonatot tartalmazza, amelyet a folytatásnak átadtak, azt úgy értelmezik, mint az együttműködő törlés elismerését.
A folytatás kap egy System.Threading.CancellationToken-t, aminek a IsCancellationRequested tulajdonsága
true. Ebben az esetben a folytatás nem indul el, és az TaskStatus.Canceled állapotra vált.A folytatás sosem fut le, mert a(z) TaskContinuationOptions argumentum által beállított feltétel nem valósult meg. Ha például egy előzmény belép egy TaskStatus.Faulted állapotba, a TaskContinuationOptions.NotOnFaulted lehetőséggel rendelkező folytonosság nem fut le, hanem átkerül a Canceled állapotba.
Ha egy tevékenység és annak folytatása ugyanannak a logikai műveletnek két részét képviseli, ugyanazt a lemondási jogkivonatot mindkét tevékenységnek átadhatja, ahogy az alábbi példában is látható. Ez egy előzményből áll, amely létrehozza a 33-tal osztható egész számok listáját, amelyeket a folytatásnak ad át. A folytatás viszont megjeleníti a listát. Az előzmény és a folytatás is rendszeresen szünetel véletlenszerű időközönként. Emellett egy System.Threading.Timer objektumot használnak a Elapsed metódus öt másodperces időtúllépési időköz utáni végrehajtásához. Ez a példa meghívja a CancellationTokenSource.Cancel metódust, ami miatt a jelenleg végrehajtó feladat meghívja a metódust CancellationToken.ThrowIfCancellationRequested . Az, hogy a CancellationTokenSource.Cancel metódus az előzmény vagy annak folytatása végrehajtásakor van-e meghívva, a véletlenszerűen létrehozott szünetek időtartamától függ. Ha az előzmény megszakad, a folytatás nem indul el. Ha az előzmény nem kerül törlésre, a token még mindig használható a folytatás megszakítására.
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
Azt is megakadályozhatja, hogy a folytatás végrehajtásra kerüljön, ha az előzményét megszakítják anélkül, hogy lemondási tokent adna meg hozzá. Adja meg a "token"-t a TaskContinuationOptions.NotOnCanceled opció megadásával a folytatás létrehozásakor, ahogyan az a következő példában látható:
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
Amikor egy folytatás Canceled állapotba kerül, hatással lehet az utána következő folytatásokra, attól függően, hogy az TaskContinuationOptions hogyan van megadva számukra.
A megsemmisített folytatások nem indulnak el.
Folytatások és gyermekfeladatok
A folytatás csak akkor fut, ha az előzmény és az összes hozzá kapcsolódó gyermekfeladat befejeződött. A folytatás nem várja meg, amíg a leválasztott gyermekfeladatok befejeződnek. Az alábbi két példa a folytatást létrehozó előzményhez csatolt és leválasztott gyermekfeladatokat szemlélteti. A következő példában a folytatás csak az összes gyermekfeladat befejezése után fut, és a példa több futtatása is minden alkalommal azonos kimenetet eredményez. A példa az TaskFactory.StartNew metódus meghívásával indítja el az előzményt, mert alapértelmezés szerint a Task.Run metódus létrehoz egy szülő feladatot, amelynek alapértelmezett feladatlétrehozási lehetősége 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
Ha azonban a gyermekfeladatok le vannak választva az előzményről, a folytatás a gyermekfeladatok állapotától függetlenül azonnal lefut, amint az előzmény befejeződött. Ennek eredményeképpen az alábbi példa több futtatása is létrehozhat változó kimenetet, amely attól függ, hogy a feladatütemező hogyan kezelte az egyes gyermekfeladatokat:
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
Az előzményfeladat végleges állapota a csatolt gyermekfeladatok végleges állapotától függ. A leválasztott gyermekfeladatok állapota nincs hatással a szülőre. További információ: Csatolt és leválasztott gyermekfeladatok.
Állapot társítása folytatásokkal
Tetszőleges állapotot társíthat egy tevékenység folytatásához. A ContinueWith metódus túlterhelt verziókat biztosít, amelyek mindegyike a Object folytatás állapotát jelképező értéket veszi fel. Ezt az állapotobjektumot később a tulajdonság használatával érheti Task.AsyncState el. Ez az állapotobjektum akkor van null , ha nem ad meg értéket.
A folytatási állapot akkor hasznos, ha az ASZinkron programozási modellt (APM) használó meglévő kódot konvertálja a TPL használatára. Az APM-ben megadhatja az objektum állapotát a Beginmetódusban , majd később a IAsyncResult.AsyncState tulajdonság használatával hozzáférhet az adott állapothoz. Ha meg szeretné őrizni ezt az állapotot, amikor az APM-et használó kódot a TPL használatára konvertálja, használja a metódust ContinueWith .
A folytatási állapot akkor is hasznos lehet, amikor a Visual Studio hibakeresőjében Task objektumokkal dolgozik. A Párhuzamos tevékenységek ablakban például a Tevékenység oszlop megjeleníti az egyes tevékenységek állapotobjektumának sztringképét. A Párhuzamos tevékenységek ablakról további információt a Feladatok ablak használata című témakörben talál.
Az alábbi példa a folytatási állapot használatát mutatja be. A folytatási feladatok láncát hozza létre. Minden tevékenység az aktuális időt, egy DateTime objektumot biztosít a stateContinueWith metódus paraméteréhez. Minden DateTime objektum azt az időpontot jelöli, amikor a folytatási feladat létrejön. Minden tevékenység eredményként egy második DateTime objektumot hoz létre, amely a tevékenység befejezésének időpontját jelöli. Az összes tevékenység befejezése után ez a példa megjeleníti a létrehozási időt és azt az időpontot, amikor az egyes folytatási tevékenységek befejeződnek.
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.
Tevékenységtípusokat visszaadó folytatások
Előfordulhat, hogy olyan folytatást kell láncolnia, amely egy típust Task ad vissza. Ezeket a tevékenységeket beágyazott tevékenységeknek nevezzük. Amikor egy szülőfeladat meghívja a Task<TResult>.ContinueWith és megad egy continuationFunction feladatot visszaadó objektumot, meghívhatja a Unwrap-t egy proxyfeladat létrehozásához, amely az aszinkron műveletet jelenti a <Task<Task<T>>>-ben vagy Task(Of Task(Of T))-ben (Visual Basic).
Az alábbi példa bemutatja, hogyan használhatja a további feladatokat visszaadó függvényeket burkoló folytatásokat. Minden folytatás kibontható, így láthatóvá válik a csomagolt belső feladat.
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))...)
További információért arról, hogyan lehet kibontani egy beágyazott feladatotUnwrap, lásd:Beágyazott feladat kibontása.
A folytatásokból származó kivételek kezelése
Az előzmény-folytatás kapcsolat nem egy szülő-gyermek kapcsolat. A folytatások által dobott kivételek nem propagálódnak az előzményre. Ezért kezelje a folytatások által kidobott kivételeket, ahogyan azokat bármely más feladatban kezelni szeretné, az alábbiak szerint:
- A Wait, WaitAll vagy WaitAny metódus, illetve annak általános megfelelőjének használatával megvárhatja a folytatást. Az előzményre és annak folytatására ugyanabban
tryaz utasításban várhat, ahogyan az alábbi példában látható:
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.
- A második folytatással megfigyelheti az Exception első folytatás tulajdonságát. Az alábbi példában egy feladat nem létező fájlból próbál beolvasni. A folytatás ezután megjeleníti a kivétel adatait az előzményfeladatban.
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'.
Mivel a TaskContinuationOptions.OnlyOnFaulted beállítással futtatták, a folytatás csak akkor fut, ha kivétel történik az előzményben. Ezért feltételezhető, hogy az előzmény tulajdonsága Exception nem az null. Ha a folytatás végrehajtódik, függetlenül attól, hogy van-e kivétel az előzményben, ellenőriznie kell, hogy az előzmény tulajdonsága nem Exception-e, mielőtt megpróbáljuk kezelni a kivételt, ahogy az alábbi kódrészlet mutatja:
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
További információ: Kivételkezelés.
- Ha a folytatás egy csatolt gyermekfeladat, amelyet a TaskContinuationOptions.AttachedToParent beállítással hoztak létre, a kivételeket a szülő újra propagálja a hívó szálra, ahogyan minden más csatolt gyermek esetében is. További információ: Csatolt és leválasztott gyermekfeladatok.