Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W programowaniu asynchronicznym często występuje jedna operacja asynchroniczna, która wywołuje drugą operację po zakończeniu. Kontynuacje umożliwiają operacjom następczym wykorzystanie wyników pierwszej operacji. Tradycyjnie kontynuacje operacji zostały wykonane przy użyciu metod callback. W bibliotece równoległej zadań (TPL) te same funkcje są udostępniane przez zadania kontynuacji. Zadanie kontynuacji (znane również jako kontynuacja) jest asynchronicznym zadaniem wywoływanym przez inne zadanie, znane jako poprzednik, kiedy poprzednik się kończy.
Kontynuacje w programowaniu są stosunkowo łatwe do użycia, ale mimo to są potężne i elastyczne. Możesz na przykład:
- Przekaż dane z poprzednika do kontynuacji.
- Określ dokładne warunki, w których zostanie wywołana kontynuacja lub nie zostanie wywołana.
- Anuluj kontynuację zanim się rozpocznie albo współpracując, gdy jest uruchomiona.
- Podaj wskazówki dotyczące sposobu planowania kontynuacji.
- Wywołaj wiele kontynuacji z tego samego antycedentu.
- Wywołaj jedną kontynuację po zakończeniu wszystkich lub jednego z wielu przeddentów.
- Łącz kolejne kontynuacje w jeden ciąg do dowolnej długości.
- Użyj kontynuacji do obsługiwania wyjątków zgłaszanych przez poprzednik.
Informacje o kontynuacjach
Kontynuacja zadania jest tworzona w stanie WaitingForActivation. Jest on aktywowany automatycznie po zakończeniu zadania lub zadań poprzednich. Podczas wywoływania Task.Start kontynuacji w kodzie użytkownika występuje wyjątek System.InvalidOperationException.
Kontynuacja jest samym Task i nie blokuje wątku, na którym została uruchomiona. Wywołaj metodę Task.Wait w celu zablokowania do momentu zakończenia zadania kontynuacji.
Tworzenie kontynuacji pojedynczego antycedenta
Tworzysz kontynuację, która jest wykonywana po zakończeniu poprzedniego zadania, wywołując metodę Task.ContinueWith. W poniższym przykładzie przedstawiono podstawowy wzorzec (w celu zapewnienia przejrzystości pominięto obsługę wyjątków). Wykonuje zadanie poprzednie taskA
, które zwraca obiekt DayOfWeek, wskazujący nazwę bieżącego dnia tygodnia. Po taskA
zakończeniu obiekt antecedent
reprezentuje swoje wyniki w metodzie kontynuacji ContinueWith
. Wynik poprzedniego zadania jest zapisywany w konsoli.
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.
Utwórz kontynuację dla wielu poprzedników
Możesz również utworzyć kontynuację, która będzie uruchamiana po zakończeniu dowolnej lub całej grupy zadań. Aby wykonać kontynuację po zakończeniu wszystkich wcześniejszych zadań, można wywołać metodę statyczną (Shared
w Visual Basic) Task.WhenAll lub metodę instancji TaskFactory.ContinueWhenAll. Aby wykonać kontynuację po zakończeniu któregokolwiek z poprzednich zadań, można wywołać metodę static (Shared
w Visual Basic) Task.WhenAny lub metodę wystąpienia TaskFactory.ContinueWhenAny .
Wywołania przeciążonych funkcji Task.WhenAll i Task.WhenAny nie blokują wątku wywołującego. Jednak zazwyczaj wywołujesz wszystkie metody poza Task.WhenAll(IEnumerable<Task>) i Task.WhenAll(Task[]), aby pobrać zwracaną właściwość Task<TResult>.Result, która blokuje wątek wywołujący.
Poniższy przykład wywołuje metodę Task.WhenAll(IEnumerable<Task>) , aby utworzyć zadanie kontynuacji, które odzwierciedla wyniki 10 poprzednich zadań. Każde zadanie poprzedzające podnosi do kwadratu wartość indeksu, która waha się od 1 do 10. Jeśli przesłanki zakończą się pomyślnie (ich właściwość Task.Status wynosi TaskStatus.RanToCompletion), właściwość Task<TResult>.Result kontynuacji jest tablicą wartości Task<TResult>.Result zwracanych przez każdą przesłankę. W przykładzie dodano je do obliczenia sumy kwadratów dla wszystkich liczb z zakresu od jednego do 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
Opcje kontynuacji
Podczas tworzenia kontynuacji jednoczęściowego zadania można użyć przeciążenia ContinueWith, z którego korzysta wartość wyliczenia System.Threading.Tasks.TaskContinuationOptions, do określenia warunków, w których rozpoczyna się kontynuacja. Można na przykład określić, że kontynuacja ma być uruchamiana tylko wtedy, gdy antecedent zakończy się pomyślnie lub tylko wtedy, gdy zakończy się w stanie uszkodzonym. Jeśli warunek nie jest spełniony, gdy poprzednik jest gotowy do wywołania ciągu dalszego, kontynuacja przechodzi bezpośrednio do stanu TaskStatus.Canceled i nie można jej uruchomić później.
Wiele metod kontynuacji w środowiskach wielozadaniowych, takich jak przeciążona metoda TaskFactory.ContinueWhenAll, również zawiera parametr System.Threading.Tasks.TaskContinuationOptions. Jednak tylko podzbiór wszystkich System.Threading.Tasks.TaskContinuationOptions elementów wyliczenia jest prawidłowy. Możesz określić System.Threading.Tasks.TaskContinuationOptions wartości, które mają odpowiedniki w wyliczeniu System.Threading.Tasks.TaskCreationOptions, takie jak TaskContinuationOptions.AttachedToParent, TaskContinuationOptions.LongRunning, i TaskContinuationOptions.PreferFairness. Jeśli określisz dowolną z opcji NotOn
lub OnlyOn
w połączeniu z kontynuacją wielu zadań, podczas wykonywania zostanie zgłoszony wyjątek ArgumentOutOfRangeException.
Aby uzyskać więcej informacji na temat opcji kontynuacji zadań, zobacz TaskContinuationOptions artykuł.
Przekazywanie danych do kontynuacji
Metoda Task.ContinueWith przekazuje odwołanie do antecedentu jako argument dla delegata użytkownika w kontynuacji. Jeśli antecedent jest obiektem System.Threading.Tasks.Task<TResult> , a zadanie zostało uruchomione do momentu jego ukończenia, kontynuacja może uzyskać dostęp do Task<TResult>.Result właściwości zadania.
Właściwość Task<TResult>.Result blokuje się do momentu ukończenia zadania. Niemniej jednak, jeśli zadanie zostało anulowane lub zawiodło, przy próbie uzyskania dostępu do właściwości Result zostanie zgłoszony wyjątek AggregateException. Możesz uniknąć tego problemu, używając opcji OnlyOnRanToCompletion, jak pokazano w poniższym przykładzie.
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?
Jeśli chcesz, aby kontynuacja przebiegła nawet wtedy, gdy poprzednia operacja nie przebiegła do pomyślnego ukończenia, musisz chronić się przed wyjątkiem. Jedną z metod jest przetestowanie właściwości Task.Status obiektu antecedent i próba uzyskania dostępu do właściwości Result tylko wtedy, gdy stan nie jest Faulted lub Canceled. Możesz również zbadać właściwość poprzednika Exception. Aby uzyskać więcej informacji, zobacz Obsługa wyjątków. Poniższy przykład modyfikuje poprzedni, aby uzyskać dostęp do jego właściwości Task<TResult>.Result tylko wtedy, gdy jego status to 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?
Anulowanie kontynuacji
Właściwość Task.Status kontynuacji jest ustawiona na TaskStatus.Canceled w następujących sytuacjach:
Zgłasza wyjątek OperationCanceledException w odpowiedzi na żądanie anulowania. Podobnie jak w przypadku każdego zadania, jeśli wyjątek zawiera ten sam token, który został przekazany do kontynuacji, jest traktowany jako potwierdzenie anulowania współpracy.
Kontynuacja jest przekazywana do System.Threading.CancellationToken, którego właściwością jest IsCancellationRequested. W takim przypadku kontynuacja nie rozpoczyna się i przechodzi do TaskStatus.Canceled stanu.
Kontynuacja nigdy nie jest uruchamiana, ponieważ warunek ustawiony przez jego TaskContinuationOptions argument nie został spełniony. Jeśli na przykład antecedent przejdzie do stanu TaskStatus.Faulted, jego kontynuacja, przekazana opcja TaskContinuationOptions.NotOnFaulted, nie zostanie uruchomiona, lecz przejdzie do stanu Canceled.
Jeśli zadanie i jego kontynuacja reprezentują dwie części tej samej operacji logicznej, możesz przekazać ten sam token anulowania do obu zadań, jak pokazano w poniższym przykładzie. Składa się z poprzednika, generującego listę liczb całkowitych podzielnych przez 33, którą przekazuje do kolejnego kroku. Kontynuacja z kolei wyświetla listę. Zarówno antecedent, jak i kontynuacja są regularnie wstrzymywane na losowe okresy czasu. Ponadto obiekt System.Threading.Timer jest używany do wykonywania metody Elapsed
po upływie pięciosekundowego przedziału czasu. Ten przykład wywołuje metodę CancellationTokenSource.Cancel, co powoduje, że aktualnie wykonywane zadanie wywołuje metodę CancellationToken.ThrowIfCancellationRequested. To, czy metoda CancellationTokenSource.Cancel jest wywoływana podczas wykonywania antecedentu lub jego kontynuacji, zależy od czasu trwania losowych wstrzymań. Jeśli antecedent zostanie anulowany, kontynuacja nie zostanie uruchomiona. Jeśli poprzednik nie został anulowany, token może być nadal używany do anulowania kontynuacji.
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
Możesz również uniemożliwić wykonanie kontynuacji, jeśli jej poprzednik zostanie anulowany, nie zapewniając kontynuacji tokenu anulacyjnego. Podaj token, określając opcję TaskContinuationOptions.NotOnCanceled podczas generowania kontynuacji, jak pokazano w poniższym przykładzie:
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
Po przejściu kontynuacji do stanu Canceled może to mieć wpływ na kolejne kontynuacje, w zależności od TaskContinuationOptions określonych dla tych kontynuacji.
Kontynuacje, które są usuwane, nie zostaną uruchomione.
Kontynuacje i zadania podrzędne
Kontynuacja nie jest uruchamiana, dopóki nie zostanie ukończona poprzednia, a wszystkie dołączone zadania podrzędne zostaną ukończone. Kontynuacja nie czeka na zakończenie odłączonych zadań podrzędnych. W poniższych dwóch przykładach pokazano podrzędne zadania, które są dołączone do poprzednika i odłączone od niego, tworząc kontynuację. W poniższym przykładzie kontynuacja jest uruchamiana tylko po zakończeniu wszystkich zadań podrzędnych, a wiele uruchomień przykładu generuje identyczne dane wyjściowe za każdym razem. W przykładzie precedens jest uruchamiany poprzez wywołanie metody TaskFactory.StartNew, ponieważ domyślnie metoda Task.Run tworzy zadanie nadrzędne, którego domyślną opcją tworzenia jest 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
Jeśli zadania podrzędne są odłączone od prekursora, ciąg dalszy jest uruchamiany zaraz po zakończeniu prekursora, niezależnie od stanu zadań podrzędnych. W związku z tym wiele uruchomień poniższego przykładu może wygenerować zmienne dane wyjściowe, które zależą od sposobu obsługi każdego zadania podrzędnego przez harmonogram zadań:
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
Stan końcowy zadania nadrzędnego jest zależny od stanu końcowego dowolnego z dołączonych zadań podrzędnych. Status odłączonych zadań podrzędnych nie wpływa na rodzica. Aby uzyskać więcej informacji, zobacz wiązane i odłączane zadania podrzędne.
Powiązanie stanu z kontynuacjami
Możesz skojarzyć dowolny stan z kontynuacją zadania. Metoda ContinueWith oferuje przeciążone wersje, z których każda przyjmuje wartość Object reprezentującą stan kontynuacji. Później możesz uzyskać dostęp do tego obiektu stanu przy użyciu Task.AsyncState właściwości . Ten obiekt stanu wynosi null
, jeśli nie podasz wartości.
Stan kontynuacji jest przydatny podczas konwertowania istniejącego kodu, który używa asynchronicznego modelu programowania (APM) do korzystania z TPL. W APM można podać stan obiektu w metodzie BeginMethod , a później można użyć IAsyncResult.AsyncState właściwości w celu uzyskania dostępu do tego stanu. Aby zachować ten stan podczas konwertowania kodu, który używa APM do korzystania z TPL, należy użyć ContinueWith metody .
Stan kontynuacji może być również przydatny podczas pracy z obiektami Task w debugerze programu Visual Studio. Na przykład w oknie Zadania równoległe kolumna Zadanie wyświetla ciąg reprezentujący obiekt stanu dla każdego zadania. Aby uzyskać więcej informacji na temat okna Zadań równoległych , zobacz Korzystanie z okna Zadania.
W poniższym przykładzie pokazano, jak używać stanu kontynuacji. Tworzy łańcuch powiązanych zadań. Każde zadanie przekazuje bieżący czas, obiekt DateTime, dla parametru state
metody ContinueWith. Każdy obiekt DateTime reprezentuje moment, w którym zostaje utworzone zadanie kontynuacji. Każde zadanie generuje w wyniku drugi DateTime obiekt reprezentujący czas zakończenia zadania. Po zakończeniu wszystkich zadań, ten przykład wyświetla czas utworzenia zadania oraz godzinę zakończenia każdego zadania kontynuacyjnego.
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.
Kontynuacje zwracające typy zadań
Czasami może być konieczne utworzenie łańcucha kontynuacji zwracającej Task typ. Te zadania są określane jako zadania zagnieżdżone. Gdy zadanie nadrzędne wywołuje Task<TResult>.ContinueWith i udostępnia continuationFunction
zwracające zadanie, można wywołać Unwrap w celu utworzenia zadania serwera proxy, które reprezentuje operację asynchroniczną <Task<Task<T>>>
lub Task(Of Task(Of T))
(Visual Basic).
W poniższym przykładzie pokazano, jak używać kontynuacji, które opakowują dodatkowe funkcje zwracające zadania. Każdą kontynuację można rozpakować, ujawniając wewnętrzne zadanie, które zostało opakowane.
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))...)
Aby uzyskać więcej informacji na temat korzystania z Unwrap, zobacz Jak odpakować zagnieżdżone zadanie.
Obsługa wyjątków zgłaszanych z kontynuacji
Relacja antecedent-kontynuacja nie jest relacją rodzic-dziecko. Wyjątki zgłaszane przez kontynuacje nie są propagowane do poprzednika. W związku z tym należy obsługiwać wyjątki zgłaszane przez kontynuacje, tak jak można je obsłużyć w innym zadaniu w następujący sposób:
- Możesz użyć Wait , WaitAll lub WaitAny albo jego ogólnego odpowiednika, aby zaczekać na kontynuację. Możesz oczekiwać na poprzednik i jego kontynuacje w tej samej
try
instrukcji, jak pokazano w poniższym przykładzie:
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.
- Możesz użyć kolejnej kontynuacji, aby monitorować Exception właściwość pierwszej kontynuacji. W poniższym przykładzie zadanie próbuje odczytać z nieistniejącego pliku. Kontynuacja następnie wyświetla informacje o wyjątku w poprzednim zadaniu.
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'.
Ponieważ była uruchamiana z opcją TaskContinuationOptions.OnlyOnFaulted, kontynuacja jest wykonywana tylko wtedy, gdy wystąpi wyjątek w poprzedniku. Zatem można założyć, że właściwość antecedenta Exception nie jest null
. Jeśli kontynuacja wykonuje się niezależnie od tego, czy wyjątek jest zgłaszany w poprzedniku, musi sprawdzić, czy właściwość Exception poprzednika nie jest null
przed podjęciem próby obsługi wyjątku, jak pokazuje następujący fragment kodu.
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
Aby uzyskać więcej informacji, zobacz Obsługa wyjątków.
- Jeśli kontynuacja jest dołączonym zadaniem podrzędnym, które zostało utworzone przy użyciu opcji TaskContinuationOptions.AttachedToParent, jej wyjątki będą propagowane przez zadanie nadrzędne z powrotem do wątku wywołującego, tak jak w przypadku każdego innego dołączonego zadania podrzędnego. Aby uzyskać więcej informacji, zobacz wiązane i odłączane zadania podrzędne.