Devamlılık görevlerini kullanarak görevleri zincirleme
Zaman uyumsuz programlamada, bir zaman uyumsuz işlemin tamamlandığında ikinci bir işlemi çağırması yaygındır. Devamlılıklar, alt işlemlerin ilk işlemin sonuçlarını tüketmesine olanak tanır. Geleneksel olarak, devamlılıklar geri çağırma yöntemleri kullanılarak yapılır. Görev Paralel Kitaplığı'nda (TPL), aynı işlevsellik devamlılık görevleri tarafından sağlanır. Devamlılık görevi (aynı zamanda devamlılık olarak da bilinir), öncül olarak bilinen başka bir görev tarafından, öncül tamamlandığında çağrılan zaman uyumsuz bir görevdir.
Devamlılıkların kullanımı nispeten kolaydır, ancak yine de güçlü ve esnektir. Örneğin, şunları yapabilirsiniz:
- Öncülden devama veri geçirme.
- Devamın çağrılacağı veya çağrılmayacak olduğu kesin koşulları belirtin.
- Devamı başlamadan önce veya çalışırken işbirliğiyle iptal edin.
- Devamın nasıl zamanlanması gerektiği hakkında ipuçları sağlayın.
- Aynı öncülden birden çok devamlılık çağırın.
- Birden çok öncülden tümü veya herhangi biri tamamlandığında bir devamlılık çağırın.
- Zincir devamı, herhangi bir rastgele uzunlukta birbiri ardına devam eder.
- Öncül tarafından oluşan özel durumları işlemek için bir devamlılık kullanın.
Devamlılıklar hakkında
Devamlılık, durumunda WaitingForActivation oluşturulan bir görevdir. Öncül görevi veya görevleri tamamlandığında otomatik olarak etkinleştirilir. Task.Start Kullanıcı kodunda devamlılık çağrısı bir System.InvalidOperationException özel durum oluşturur.
Devamlılık kendisidir Task ve başlatıldığı iş parçacığını engellemez. Task.Wait Devamlılık görevi bitene kadar engellemek için yöntemini çağırın.
Tek bir öncül için devamlılık oluşturma
Yöntemini çağırarak Task.ContinueWith öncül tamamlandığında yürütülen bir devamlılık oluşturursunuz. Aşağıdaki örnekte temel desen gösterilmektedir (netlik için özel durum işleme atlanmıştır). Haftanın geçerli gününün adını gösteren bir DayOfWeek nesne döndüren bir öncül görev taskA
yürütür. taskA
tamamlandığında, antecedent
devamlılık yönteminde ContinueWith
sonuçlarını temsil eder. Öncül görevin sonucu konsola yazılır.
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.
Birden çok öncül için devamlılık oluşturma
Ayrıca, bir görev grubunun herhangi biri veya tümü tamamlandığında çalışacak bir devamlılık oluşturabilirsiniz. Tüm öncül görevler tamamlandığında devamı yürütmek için statik (Shared
Visual Basic'te) Task.WhenAll yöntemini veya örnek TaskFactory.ContinueWhenAll yöntemini çağırabilirsiniz. Öncül görevlerden herhangi biri tamamlandığında devamı yürütmek için statik (Shared
Visual Basic'te) Task.WhenAny yöntemini veya örnek TaskFactory.ContinueWhenAny yöntemini çağırabilirsiniz.
Task.WhenAll ve Task.WhenAny aşırı yüklemelerine yapılan çağrılar çağrı iş parçacığını engellemez. Ancak, çağrı iş parçacığını engelleyen döndürülen Task<TResult>.Result özelliği almak için genellikle ve Task.WhenAll(Task[]) yöntemleri dışında Task.WhenAll(IEnumerable<Task>) tümünü çağırırsınız.
Aşağıdaki örnek, 10 öncül görevinin sonuçlarını yansıtan bir devamlılık görevi oluşturmak için yöntemini çağırır Task.WhenAll(IEnumerable<Task>) . Her öncül görev, bir ile 10 arasında bir dizin değerinin karesini alır. Öncüller başarıyla tamamlanırsa (özellikleri Task.Status ), TaskStatus.RanToCompletionTask<TResult>.Result devamlılık özelliği her bir öncül tarafından döndürülen değerlerin Task<TResult>.Result bir dizisidir. Örnek, bir ile 10 arasındaki tüm sayıların karelerinin toplamını hesaplamak için bunları ekler:
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
Devam seçenekleri
Tek görevli bir devamlılık oluşturduğunuzda, devamın başlatıldığı koşulları belirtmek için numaralandırma değeri alan bir System.Threading.Tasks.TaskContinuationOptions aşırı yükleme kullanabilirsinizContinueWith. Örneğin, devamlılık işleminin yalnızca öncül başarıyla tamamlanırsa veya yalnızca hatalı durumda tamamlanırsa çalıştırılacağını belirtebilirsiniz. Öncül devamlılığı çağırmaya hazır olduğunda koşul doğru değilse, devamlılık doğrudan TaskStatus.Canceled duruma geçirilir ve daha sonra başlatılamaz.
Yöntemin aşırı yüklemeleri TaskFactory.ContinueWhenAll gibi birçok çok görevli devamlılık yöntemi de bir System.Threading.Tasks.TaskContinuationOptions parametre içerir. Ancak, tüm System.Threading.Tasks.TaskContinuationOptions numaralandırma üyelerinin yalnızca bir alt kümesi geçerlidir. Numaralandırmada System.Threading.Tasks.TaskCreationOptions , ve TaskContinuationOptions.PreferFairnessgibi TaskContinuationOptions.LongRunningTaskContinuationOptions.AttachedToParentkarşılıkları olan değerleri belirtebilirsinizSystem.Threading.Tasks.TaskContinuationOptions. Veya OnlyOn
seçeneklerinden herhangi birini NotOn
çok görevli devamlılık ile belirtirseniz, çalışma zamanında bir ArgumentOutOfRangeException özel durum oluşturulur.
Görev devamlılığı seçenekleri hakkında daha fazla bilgi için makaleye TaskContinuationOptions bakın.
Devama veri geçirme
yöntemi, Task.ContinueWith devamlılık kullanıcı temsilcisine bağımsız değişken olarak bir başvuru geçirir. Öncül bir System.Threading.Tasks.Task<TResult> nesneyse ve görev tamamlanana kadar çalıştırıldıysa, devamlılık görevin özelliğine Task<TResult>.Result erişebilir.
Task<TResult>.Result Görev tamamlanana kadar özellik blokları. Ancak, görev iptal edildiyse veya hata oluştuysa, özelliğe erişmeye Result çalışmak bir AggregateException özel durum oluşturur. Aşağıdaki örnekte gösterildiği gibi seçeneğini kullanarak OnlyOnRanToCompletion bu sorundan kaçınabilirsiniz:
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?
Öncül başarıyla tamamlanmamış olsa bile devamın çalışmasını istiyorsanız, özel duruma karşı korumanız gerekir. Yaklaşımlardan biri, öncül özelliğini test Task.Status etmek ve yalnızca durum veya Canceleddeğilse Faulted özelliğine erişmeye Result çalışmaktır. Ayrıca, öncül Exception özelliğini de inceleyebilirsiniz. Daha fazla bilgi için bkz . Özel Durum İşleme. Aşağıdaki örnek, önceki örneği yalnızca durumu TaskStatus.RanToCompletionolduğunda antecedent özelliğine Task<TResult>.Result erişecek şekilde değiştirir:
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?
Devamı iptal etme
Bir Task.Status devamlılık özelliği aşağıdaki durumlarda olarak TaskStatus.Canceled ayarlanır:
İptal isteğine yanıt olarak bir OperationCanceledException özel durum oluşturur. Herhangi bir görevde olduğu gibi, özel durum devama geçirilen belirteçle aynı belirteci içeriyorsa, işbirliğine dayalı iptal bildirimi olarak kabul edilir.
Devamlılık, özelliği olan bir System.Threading.CancellationToken geçirilir IsCancellationRequested
true
. Bu durumda, devam başlatılmaz ve duruma geçiş yapılır TaskStatus.Canceled .Bağımsız değişkeni tarafından TaskContinuationOptions ayarlanan koşul karşılanmadığından devamlılık hiçbir zaman çalışmaz. Örneğin, bir öncül bir TaskStatus.Faulted duruma geçerse, seçeneği geçirilen TaskContinuationOptions.NotOnFaulted devamı çalışmaz ancak duruma geçer Canceled .
Bir görev ve devamı aynı mantıksal işlemin iki parçasını temsil ederse, aşağıdaki örnekte gösterildiği gibi her iki göreve de aynı iptal belirtecini geçirebilirsiniz. 33 ile bölünebilen tamsayıların listesini oluşturan ve devama geçiren bir öncülden oluşur. Devamı sırayla listeyi görüntüler. Hem öncül hem de devamlılık rastgele aralıklar için düzenli olarak duraklatılır. Ayrıca, beş saniyelik zaman aşımı aralığından Elapsed
sonra yöntemini yürütmek için bir System.Threading.Timer nesne kullanılır. Bu örnek yöntemini çağırır CancellationTokenSource.Cancel ve bu da şu anda yürütülmekte olan görevin yöntemini çağırmasına CancellationToken.ThrowIfCancellationRequested neden olur. Yöntemin CancellationTokenSource.Cancel öncül veya devamı yürütülürken çağrılıp çağrılmayacağı, rastgele oluşturulan duraklamaların süresine bağlıdır. Öncül iptal edilirse, devam başlatılmaz. Öncül iptal edilmediyse, devamı iptal etmek için belirteç hala kullanılabilir.
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: {0}", task.Status);
Console.WriteLine("Continuation Status: {0}", 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
Ayrıca, bir devamlılık belirteci sağlamadan öncül iptal edilirse devamın yürütülmesini engelleyebilirsiniz. Aşağıdaki örnekte gösterildiği gibi devamlılığı oluştururken seçeneğini belirterek TaskContinuationOptions.NotOnCanceled belirteci sağlayın:
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
Bir devamlılık duruma geçtikten Canceled sonra, bu devamlılıklar için belirtilene bağlı TaskContinuationOptions olarak izleyen devamlılıkları etkileyebilir.
Atılan devamlılıklar başlatılmaz.
Devamlılıklar ve alt görevler
Bir devamlılık, öncül ve ekli tüm alt görevleri tamamlanana kadar çalışmaz. Devam, ayrılmış alt görevlerin tamamlanmasını beklemez. Aşağıdaki iki örnek, bir devamlılık oluşturan bir öncüle eklenmiş ve ondan ayrılmış alt görevleri göstermektedir. Aşağıdaki örnekte, devamlılık yalnızca tüm alt görevler tamamlandıktan sonra çalıştırılır ve örneğin birden çok çalıştırması her seferinde aynı çıkışı üretir. Varsayılan olarak Task.Run yöntemi varsayılan görev oluşturma seçeneği TaskCreationOptions.DenyChildAttacholan bir üst görev oluşturduğundan örnek, yöntemini çağırarak TaskFactory.StartNew öncül'leri başlatır.
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
Ancak alt görevler öncülden ayrılırsa, alt görevlerin durumundan bağımsız olarak, devamlılık, öncül sonlandırıldığı anda çalışır. Sonuç olarak, aşağıdaki örneğin birden çok çalıştırması, görev zamanlayıcının her alt görevi nasıl işlediğine bağlı olarak değişken çıkış oluşturabilir:
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
Öncül görevin son durumu, ekli alt görevlerin son durumuna bağlıdır. Ayrılmış alt görevlerin durumu üst öğeyi etkilemez. Daha fazla bilgi için bkz . Ekli ve Ayrılmış Alt Görevler.
Durumu devamlılıklarla ilişkilendirme
Rastgele durumu bir görev devamlılığı ile ilişkilendirebilirsiniz. yöntemi, ContinueWith her birinin devamlılık durumunu temsil eden bir Object değer alan aşırı yüklenmiş sürümler sağlar. Daha sonra özelliğini kullanarak bu durum nesnesine Task.AsyncState erişebilirsiniz. Bu durum nesnesi, null
bir değer sağlamazsanız olur.
Devamlılık durumu, Zaman Uyumsuz Programlama Modeli'ni (APM) kullanan mevcut kodu TPL'yi kullanacak şekilde dönüştürdüğünüzde kullanışlıdır. APM'de BeginYöntemi yönteminde nesne durumu sağlayabilir ve daha sonra bu duruma erişmek için özelliğini kullanabilirsinizIAsyncResult.AsyncState. TPL'yi kullanmak üzere APM kullanan bir kodu dönüştürürken bu durumu korumak için yöntemini kullanırsınız ContinueWith .
Devam durumu, Visual Studio hata ayıklayıcısında nesnelerle Task çalışırken de yararlı olabilir. Örneğin, Paralel Görevler penceresinde Görev sütunu her görev için durum nesnesinin dize gösterimini görüntüler. Paralel Görevler penceresi hakkında daha fazla bilgi için bkz. Görevler Penceresini Kullanma.
Aşağıdaki örnekte devamlılık durumunun nasıl kullanılacağı gösterilmektedir. Bir devamlılık görevleri zinciri oluşturur. Her görev, yönteminin parametresi için state
geçerli saati ( bir DateTime nesnesi) ContinueWith sağlar. Her DateTime nesne, devamlılık görevinin oluşturulduğu saati temsil eder. Her görev, sonucu olarak görevin bitiş zamanını temsil eden ikinci DateTime bir nesne oluşturur. Tüm görevler tamamlandıktan sonra, bu örnekte oluşturma zamanı ve her devamlılık görevinin bitiş saati görüntülenir.
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.
Görev türlerini döndüren devamlılıklar
Bazen bir tür döndüren Task bir devamlılığı zincirlemeniz gerekebilir. Bu görevler iç içe görevler olarak adlandırılır. Üst görev çağırdığında Task<TResult>.ContinueWith ve görev döndüren bir continuationFunction
görev sağladığında, veya Task(Of Task(Of T))
(Visual Basic) zaman uyumsuz işlemini <Task<Task<T>>>
temsil eden bir ara sunucu görevi oluşturmak için çağırabilirsinizUnwrap.
Aşağıdaki örnekte, ek görev döndüren işlevleri sarmalayan devamlılıkların nasıl kullanılacağı gösterilmektedir. Her devamlılık, sarmalanan iç görevi ortaya çıkararak açılabilir.
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))...)
kullanma Unwraphakkında daha fazla bilgi için bkz . Nasıl yapılır: İç içe görev sarmayı kaldırma.
Devamlılıklardan oluşan özel durumları işleme
Antecedent-continuation ilişkisi bir üst-alt ilişki değildir. Devamlılıklar tarafından oluşan özel durumlar, öncül öğeye yayılmaz. Bu nedenle, devamlılıklar tarafından oluşturulan özel durumları aşağıdaki gibi başka bir görevde işleyecek şekilde işleyin:
- Devamı Waitbeklemek için , WaitAll, veya WaitAny yöntemini veya genel karşılık gelenini kullanabilirsiniz. Aşağıdaki örnekte gösterildiği gibi, aynı
try
deyimde bir öncül ve devamını bekleyebilirsiniz:
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.
- İlk devamlılık özelliğini gözlemlemek Exception için ikinci bir devamlılık kullanabilirsiniz. Aşağıdaki örnekte, bir görev var olmayan bir dosyadan okumaya çalışır. Devam daha sonra öncül görevdeki özel durum hakkındaki bilgileri görüntüler.
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'.
seçeneğiyle çalıştırıldığından TaskContinuationOptions.OnlyOnFaulted , devamlılık yalnızca öncülde bir özel durum oluştuğunda yürütülür. Bu nedenle, öncül Exception özelliğinin olmadığını null
varsayabilir. Devamlılık, öncülde bir özel durum oluşturup oluşturmadığını yürütürse, aşağıdaki kod parçasında gösterildiği gibi, özel durumu işlemeye çalışmadan önce öncül Exception özelliğinin olup olmadığını null
denetlemesi gerekir:
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
Daha fazla bilgi için bkz . Özel Durum İşleme.
- Devamlılık, seçeneği kullanılarak TaskContinuationOptions.AttachedToParent oluşturulmuş ekli bir alt görevse, özel durumları üst öğe tarafından çağrılan iş parçacığına geri yayılır, diğer ekli alt görevlerde olduğu gibi. Daha fazla bilgi için bkz . Ekli ve Ayrılmış Alt Görevler.