如何:取消任务及其子级

这些示例演示如何执行以下任务:

  1. 创建和启动可取消的任务。

  2. 将取消标记传递到您的用户委托,并可选择传递到任务实例。

  3. 在用户委托中发出取消请求通知并对这些请求做出响应。

  4. (可选)在调用线程上发出有关任务已取消的通知。

调用线程不会强制结束任务;它只会发出有关已请求取消的通知。 如果任务已经在运行,则由用户委托负责通知请求并相应做出响应。 如果在任务运行之前请求了取消,则用户委托从不会运行,并且任务对象将转换为 Canceled 状态。

示例

此示例演示如何终止 Task 及其子级以响应取消请求。 它还演示,当用户委托通过引发 OperationCanceledException 终止时,调用线程可以选择使用 Wait 方法或 WaitAll 方法来等待任务完成。 在这种情况下,委托必须使用 try catch 块来处理调用线程上的异常。

' How to: Cancel a Task and Its Children
Imports System.Threading
Imports System.Threading.Tasks

Module CancelATask

    Sub Main()

        Console.WriteLine("Press any key to start. Press 'c' to cancel.")
        Console.ReadKey()

        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 tasks(10) As Task

        ' 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.
        tasks(0) = Task.Factory.StartNew(Sub() DoSomeWork(1, token), token)

        ' 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.
        tasks(1) = Task.Factory.StartNew(Sub()

                                             ' Create some cancelable child tasks.
                                             For i As Integer = 2 To 10

                                                 ' For each child task, pass the same token
                                                 ' to each user delegate and to StartNew.
                                                 tasks(i) = Task.Factory.StartNew(Sub(iteration) DoSomeWork(iteration, token), i, token)
                                                 ' Passing 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)

        ' Give the tasks a second to start.
        Thread.Sleep(1000)

        ' Request cancellation from the UI thread.
        If Console.ReadKey().KeyChar = "c"c Then

            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 OperationCanceledExceptions that are thrown. If you do 
            ' not wait, no OCE 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)

        Catch e As AggregateException

            ' For demonstration purposes, show the OCE message.
            For Each v In e.InnerExceptions
                Console.WriteLine("msg: " + v.Message)
            Next
        End Try

        ' Prove that the tasks are now all in a canceled state.
        For i As Integer = 0 To tasks.Length
            Console.WriteLine("task(0) status is now 1", i, tasks(i).Status)
        Next

        ' Keep the console window open while the
        ' task completes its output.
        Console.ReadLine()
    End Sub

    Sub DoSomeWork(ByVal taskNum As Integer, ByVal ct As CancellationToken)

        ' Was cancellation already requested?
        If ct.IsCancellationRequested = True Then
            Console.WriteLine("We were cancelled before we got started.")
            Console.WriteLine("Press Enter to quit.")
            ct.ThrowIfCancellationRequested()
        End If

        Dim maxIterations As Integer = 1000

        ' NOTE!!! An "OperationCanceledException 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 3000
                sw.SpinOnce()
            Next
            Console.WriteLine("...0 ", taskNum)
            If ct.IsCancellationRequested Then

                Console.WriteLine("bye from 0.", taskNum)
                Console.WriteLine("\nPress Enter to quit.")

                ct.ThrowIfCancellationRequested()
            End If
        Next

    End Sub

End Module

namespace CancellationWithOCE
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Press any key to start. Press 'c' to cancel.");
            Console.ReadKey();

            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[] tasks = new Task[10];

            // 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.
            tasks[0] = Task.Factory.StartNew(() => DoSomeWork(1, token), token);

            // 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.
            tasks[1] = Task.Factory.StartNew(() =>
            {
                // Create some cancelable child tasks.
                for (int i = 2; i < 10; i++)
                {
                    // For each child task, pass the same token
                    // to each user delegate and to StartNew.
                    tasks[i] = Task.Factory.StartNew(iteration =>
                                DoSomeWork((int)iteration, token), i, token);
                }
                // Passing 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);

            // Give the tasks a second to start.
            Thread.Sleep(1000);

            // Request cancellation from the UI thread.
            if (Console.ReadKey().KeyChar == '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 OperationCanceledExceptions that are thrown. If you do 
                // not wait, no OCE is thrown if the token that was passed to the 
                // StartNew method is the same token that requested the cancellation.

                #region Optional_WaitOnTasksToComplete
                try
                {
                    Task.WaitAll(tasks);
                }
                catch (AggregateException e)
                {
                    // For demonstration purposes, show the OCE message.
                    foreach (var v in e.InnerExceptions)
                        Console.WriteLine("msg: " + v.Message);
                }

                // Prove that the tasks are now all in a canceled state.
                for (int i = 0; i < tasks.Length; i++)
                    Console.WriteLine("task[{0}] status is now {1}", i, tasks[i].Status);
                #endregion
            }

            // Keep the console window open while the
            // task completes its output.
            Console.ReadLine();
        }

        static void DoSomeWork(int taskNum, CancellationToken ct)
        {
            // Was cancellation already requested?
            if (ct.IsCancellationRequested)
            {
                Console.WriteLine("We were cancelled before we got started.");
                Console.WriteLine("Press Enter to quit.");
                ct.ThrowIfCancellationRequested();
            }
            int maxIterations = 1000;

            // NOTE!!! A benign "OperationCanceledException was unhandled
            // by user code" error might be raised here. Press F5 to continue. Or,
            //  to avoid the error, uncheck the "Enable Just My Code"
            // option under Tools > Options > Debugging.
            for (int i = 0; i < maxIterations; i++)
            {
                // Do a bit of work. Not too much.
                var sw = new SpinWait();
                for (int j = 0; j < 3000; j++) sw.SpinOnce();
                Console.WriteLine("...{0} ", taskNum);
                if (ct.IsCancellationRequested)
                {
                    Console.WriteLine("bye from {0}.", taskNum);
                    Console.WriteLine("\nPress Enter to quit.");

                    ct.ThrowIfCancellationRequested();
                }
            }
        }
    }
}

System.Threading.Tasks.Task 类与基于 System.Threading.CancellationTokenSourceSystem.Threading.CancellationToken 类型的取消模型完全集成。 有关更多信息,请参见取消任务取消

请参见

参考

System.Threading.CancellationTokenSource

System.Threading.CancellationToken

System.Threading.Tasks.Task

System.Threading.Tasks.Task<TResult>

概念

嵌套任务和子任务

在 PLINQ 和 TPL 中的 Lambda 表达式

其他资源

任务并行(任务并行库)