如何:取消工作及其子系How to: Cancel a Task and Its Children

這些範例示範如何執行下列工作:These examples show how to perform the following tasks:

  1. 建立和啟動可取消的工作。Create and start a cancelable task.

  2. 將取消權杖傳送至您的使用者委派或工作執行個體 (選擇性)。Pass a cancellation token to your user delegate and optionally to the task instance.

  3. 注意並回應使用者委派中的取消要求。Notice and respond to the cancellation request in your user delegate.

  4. 注意已取消工作的呼叫端執行緒 (選擇性)。Optionally notice on the calling thread that the task was canceled.

呼叫端執行緒不會強制結束工作;它只會通知已要求取消。The calling thread does not forcibly end the task; it only signals that cancellation is requested. 如果工作已在執行,則是由使用者委派來通知要求並適當回應。If the task is already running, it is up to the user delegate to notice the request and respond appropriately. 如果在工作執行前要求取消,則永遠不會執行使用者委派,且工作物件會轉換成 Canceled 狀態。If cancellation is requested before the task runs, then the user delegate is never executed and the task object transitions into the Canceled state.

範例Example

此範例示範如何終止 Task 及其子系,以回應取消要求。This example shows how to terminate a Task and its children in response to a cancellation request. 這個範例也會說明在使用者委派藉由擲回 TaskCanceledException 結束時,呼叫端執行緒可以選擇性地使用 Wait 方法或 WaitAll 方法等候工作完成。It also shows that when a user delegate terminates by throwing a TaskCanceledException, the calling thread can optionally use the Wait method or WaitAll method to wait for the tasks to finish. 在這種情況下,您必須使用 try/catch 區塊來處理呼叫端執行緒上的例外狀況。In this case, you must use a try/catch block to handle the exceptions on the calling thread.

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
    public static async Task Main()
    {
        var tokenSource = new CancellationTokenSource();
        var token = tokenSource.Token;

        // Store references to the tasks so that we can wait on them and
        // observe their status after cancellation.
        Task t;
        var tasks = new ConcurrentBag<Task>();

        Console.WriteLine("Press any key to begin tasks...");
        Console.ReadKey(true);
        Console.WriteLine("To terminate the example, press 'c' to cancel and exit...");
        Console.WriteLine();

        // Request cancellation of a single task when the token source is canceled.
        // Pass the token to the user delegate, and also to the task so it can
        // handle the exception correctly.
        t = Task.Run(() => DoSomeWork(1, token), token);
        Console.WriteLine("Task {0} executing", t.Id);
        tasks.Add(t);

        // Request cancellation of a task and its children. Note the token is passed
        // to (1) the user delegate and (2) as the second argument to Task.Run, so
        // that the task instance can correctly handle the OperationCanceledException.
        t = Task.Run(() =>
        {
            // Create some cancelable child tasks.
            Task tc;
            for (int i = 3; i <= 10; i++)
            {
                // For each child task, pass the same token
                // to each user delegate and to Task.Run.
                tc = Task.Run(() => DoSomeWork(i, token), token);
                Console.WriteLine("Task {0} executing", tc.Id);
                tasks.Add(tc);
                // Pass the same token again to do work on the parent task.
                // All will be signaled by the call to tokenSource.Cancel below.
                DoSomeWork(2, token);
            }
        }, token);

        Console.WriteLine("Task {0} executing", t.Id);
        tasks.Add(t);

        // Request cancellation from the UI thread. 
        char ch = Console.ReadKey().KeyChar;
        if (ch == 'c' || ch == 'C')
        {
            tokenSource.Cancel();
            Console.WriteLine("\nTask cancellation requested.");

            // Optional: Observe the change in the Status property on the task. 
            // It is not necessary to wait on tasks that have canceled. However, 
            // if you do wait, you must enclose the call in a try-catch block to 
            // catch the TaskCanceledExceptions that are thrown. If you do  
            // not wait, no exception is thrown if the token that was passed to the  
            // Task.Run method is the same token that requested the cancellation.
        }

        try
        {
            await Task.WhenAll(tasks.ToArray());
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine($"\n{nameof(OperationCanceledException)} thrown\n");
        }
        finally
        {
            tokenSource.Dispose();
        }

        // Display status of all tasks. 
        foreach (var task in tasks)
            Console.WriteLine("Task {0} status is now {1}", task.Id, task.Status);
    }

    static void DoSomeWork(int taskNum, CancellationToken ct)
    {
        // Was cancellation already requested?
        if (ct.IsCancellationRequested)
        {
            Console.WriteLine("Task {0} was cancelled before it got started.",
                              taskNum);
            ct.ThrowIfCancellationRequested();
        }

        int maxIterations = 100;

        // NOTE!!! A "TaskCanceledException was unhandled
        // by user code" error will be raised here if "Just My Code"
        // is enabled on your computer. On Express editions JMC is
        // enabled and cannot be disabled. The exception is benign.
        // Just press F5 to continue executing your code.
        for (int i = 0; i <= maxIterations; i++)
        {
            // Do a bit of work. Not too much.
            var sw = new SpinWait();
            for (int j = 0; j <= 100; j++)
                sw.SpinOnce();

            if (ct.IsCancellationRequested)
            {
                Console.WriteLine("Task {0} cancelled", taskNum);
                ct.ThrowIfCancellationRequested();
            }
        }
    }
}
// The example displays output like the following:
//       Press any key to begin tasks...
//    To terminate the example, press 'c' to cancel and exit...
//    
//    Task 1 executing
//    Task 2 executing
//    Task 3 executing
//    Task 4 executing
//    Task 5 executing
//    Task 6 executing
//    Task 7 executing
//    Task 8 executing
//    c
//    Task cancellation requested.
//    Task 2 cancelled
//    Task 7 cancelled
//    
//    OperationCanceledException thrown
//    
//    Task 2 status is now Canceled
//    Task 1 status is now RanToCompletion
//    Task 8 status is now Canceled
//    Task 7 status is now Canceled
//    Task 6 status is now RanToCompletion
//    Task 5 status is now RanToCompletion
//    Task 4 status is now RanToCompletion
//    Task 3 status is now RanToCompletion
Imports System.Collections.Concurrent
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Sub Main()
        Dim tokenSource As New CancellationTokenSource()
        Dim token As CancellationToken = tokenSource.Token

        ' Store references to the tasks so that we can wait on them and  
        ' observe their status after cancellation. 
        Dim t As Task
        Dim tasks As New ConcurrentBag(Of Task)()

        Console.WriteLine("Press any key to begin tasks...")
        Console.ReadKey(True)
        Console.WriteLine("To terminate the example, press 'c' to cancel and exit...")
        Console.WriteLine()
        
        ' Request cancellation of a single task when the token source is canceled. 
        ' Pass the token to the user delegate, and also to the task so it can  
        ' handle the exception correctly.
        t = Task.Factory.StartNew(Sub() DoSomeWork(1, token), token)
        Console.WriteLine("Task {0} executing", t.Id)
        tasks.Add(t)

        ' Request cancellation of a task and its children. Note the token is passed 
        ' to (1) the user delegate and (2) as the second argument to StartNew, so  
        ' that the task instance can correctly handle the OperationCanceledException.
        t = Task.Factory.StartNew(Sub() 
                                     ' Create some cancelable child tasks.  
                                     Dim tc As Task
                                     For i As Integer = 3 To 10
                                        ' For each child task, pass the same token 
                                        ' to each user delegate and to StartNew.
                                        tc = Task.Factory.StartNew(Sub(iteration) DoSomeWork(iteration, token), i, token)
                                        Console.WriteLine("Task {0} executing", tc.Id)
                                        tasks.Add(tc)
                                        ' Pass the same token again to do work on the parent task.  
                                        ' All will be signaled by the call to tokenSource.Cancel below.
                                        DoSomeWork(2, token)
                                     Next 
                                  End Sub, 
                                  token)
                                           
        Console.WriteLine("Task {0} executing", t.Id)
        tasks.Add(t)

        ' Request cancellation from the UI thread. 
        Dim ch As Char = Console.ReadKey().KeyChar
        If ch = "c"c Or ch = "C"c Then
            tokenSource.Cancel()
            Console.WriteLine(vbCrLf + "Task cancellation requested.")

            ' Optional: Observe the change in the Status property on the task. 
            ' It is not necessary to wait on tasks that have canceled. However, 
            ' if you do wait, you must enclose the call in a try-catch block to 
            ' catch the TaskCanceledExceptions that are thrown. If you do  
            ' not wait, no exception is thrown if the token that was passed to the  
            ' StartNew method is the same token that requested the cancellation. 
        End If 

        Try
            Task.WaitAll(tasks.ToArray())
        Catch e As AggregateException
           Console.WriteLine()
           Console.WriteLine("AggregateException thrown with the following inner exceptions:")
           ' Display information about each exception. 
           For Each v In e.InnerExceptions
              If TypeOf v Is TaskCanceledException
                 Console.WriteLine("   TaskCanceledException: Task {0}", 
                                   DirectCast(v, TaskCanceledException).Task.Id)
              Else
                 Console.WriteLine("   Exception: {0}", v.GetType().Name)
              End If
           Next 
           Console.WriteLine()
        Finally
           tokenSource.Dispose()
        End Try 

        ' Display status of all tasks. 
        For Each t In tasks
            Console.WriteLine("Task {0} status is now {1}", t.Id, t.Status)
        Next 
    End Sub 

    Sub DoSomeWork(ByVal taskNum As Integer, ByVal ct As CancellationToken)
        ' Was cancellation already requested? 
        If ct.IsCancellationRequested = True Then
            Console.WriteLine("Task {0} was cancelled before it got started.",
                              taskNum)
            ct.ThrowIfCancellationRequested()
        End If 

        Dim maxIterations As Integer = 100

        ' NOTE!!! A "TaskCanceledException was unhandled 
        ' by user code" error will be raised here if "Just My Code" 
        ' is enabled on your computer. On Express editions JMC is 
        ' enabled and cannot be disabled. The exception is benign. 
        ' Just press F5 to continue executing your code. 
        For i As Integer = 0 To maxIterations
            ' Do a bit of work. Not too much. 
            Dim sw As New SpinWait()
            For j As Integer = 0 To 100
                sw.SpinOnce()
            Next
            If ct.IsCancellationRequested Then
                Console.WriteLine("Task {0} cancelled", taskNum)
                ct.ThrowIfCancellationRequested()
            End If 
        Next 
    End Sub 
End Module
' The example displays output like the following:
'    Press any key to begin tasks...
'    To terminate the example, press 'c' to cancel and exit...
'    
'    Task 1 executing
'    Task 2 executing
'    Task 3 executing
'    Task 4 executing
'    Task 5 executing
'    Task 6 executing
'    Task 7 executing
'    Task 8 executing
'    c
'    Task cancellation requested.
'    Task 2 cancelled
'    Task 7 cancelled
'    
'    AggregateException thrown with the following inner exceptions:
'       TaskCanceledException: Task 2
'       TaskCanceledException: Task 8
'       TaskCanceledException: Task 7
'    
'    Task 2 status is now Canceled
'    Task 1 status is now RanToCompletion
'    Task 8 status is now Canceled
'    Task 7 status is now Canceled
'    Task 6 status is now RanToCompletion
'    Task 5 status is now RanToCompletion
'    Task 4 status is now RanToCompletion
'    Task 3 status is now RanToCompletion

System.Threading.Tasks.Task 類別已和基礎是 System.Threading.CancellationTokenSourceSystem.Threading.CancellationToken 型別的取消模型完全整合。The System.Threading.Tasks.Task class is fully integrated with the cancellation model that is based on the System.Threading.CancellationTokenSource and System.Threading.CancellationToken types. 如需詳細資訊,請參閱受控執行緒中的取消作業工作取消For more information, see Cancellation in Managed Threads and Task Cancellation.

另請參閱See also