如何:侦听多个取消请求

此示例演示如何同时侦听两个取消标记,以便您可以取消操作(如果任一标记请求取消)。

注意注意

当启用“仅我的代码”时,在某些情况下,Visual Studio 将在引发异常的行上中断运行,并显示错误消息“异常未由用户代码处理”。此错误是良性的。可以按 F5 从中断处继续运行,并查看在以下示例中演示的异常处理行为。若要阻止 Visual Studio 在出现第一个错误时中断运行,只需在“工具”->“选项”->“调试”->“常规”下,取消选中“仅我的代码”复选框即可。

示例

在下面的示例中,使用 [M:M:System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken[])] 方法将两个标记连接成一个标记。 这使得可以将标记传递给只采用一个取消标记作为参数的方法。 此示例演示一种常见情形,即方法必须检查从类外传入的标记和在类中生成的标记。

Class LinkedTokenSourceDemo

    Shared Sub Main()

        Dim worker As New WorkerWithTimer()
        Dim cts As New CancellationTokenSource()

        ' Task for UI thread, so we can call Task.Wait wait on the main thread.
        Task.Factory.StartNew(Sub()

                                  Console.WriteLine("Press 'c' to cancel within 3 seconds after work begins.")
                                  Console.WriteLine("Or let the task time out by doing nothing.")
                                  If Console.ReadKey().KeyChar = "c"c Then
                                      cts.Cancel()
                                  End If
                              End Sub
    )
        ' Let the user read the UI message.
        Thread.Sleep(1000)

        ' Start the worker task.
        Dim t As Task = Task.Factory.StartNew(Sub() worker.DoWork(cts.Token), cts.Token)

        Try

            t.Wait()


        Catch ae As AggregateException

            For Each inner In ae.InnerExceptions
                Console.WriteLine(inner.Message)
            Next
        End Try

        Console.WriteLine("Press any key to exit.")
        Console.ReadKey()
    End Sub
End Class

Class WorkerWithTimer

    Dim internalTokenSource As CancellationTokenSource
    Dim token As CancellationToken
    Dim myTimer As Timer

    Public Sub WorkerWithTimer()

        internalTokenSource = New CancellationTokenSource()
        token = internalTokenSource.Token

        ' A toy cancellation trigger that times out after 3 seconds
        ' if the user does not press 'c'.
        myTimer = New Timer(New TimerCallback(AddressOf CancelAfterTimeout), Nothing, 3000, 3000)
    End Sub


    Public Sub DoWork(ByVal externalToken As CancellationToken)

        ' Create a new token that combines the internal and external tokens.
        Dim internalToken As CancellationToken = internalTokenSource.Token
        Dim linkedCts As CancellationTokenSource =
        CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken)
        Using (linkedCts)
            Try
                DoWorkInternal(linkedCts.Token)

            Catch e As OperationCanceledException
                If e.CancellationToken = internalToken Then
                    Console.WriteLine("Operation timed out.")

                ElseIf e.CancellationToken = externalToken Then
                    Console.WriteLine("Canceled by external token.")
                    externalToken.ThrowIfCancellationRequested()
                End If

            End Try
        End Using
    End Sub


    Private Sub DoWorkInternal(ByVal token As CancellationToken)

        For i As Integer = 0 To 1000

            If token.IsCancellationRequested Then

                ' We need to dispose the timer if cancellation
                ' was requested by the external token.
                myTimer.Dispose()

                ' Output for demonstration purposes.
                Console.WriteLine("\r\nCancelling per request.")

                ' Throw the exception.
                token.ThrowIfCancellationRequested()
            End If

            ' Simulating work.
            Thread.SpinWait(7500000)
            Console.Write("working... ")
        Next
    End Sub

    Public Sub CancelAfterTimeout(ByVal state As Object)

        Console.WriteLine("\r\nTimer fired.")
        internalTokenSource.Cancel()
        myTimer.Dispose()
    End Sub
End Class
namespace WaitForMultiple
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    class LinkedTokenSourceDemo
    {
        static void Main()
        {
            WorkerWithTimer worker = new WorkerWithTimer();
            CancellationTokenSource cts = new CancellationTokenSource();

            // Task for UI thread, so we can call Task.Wait wait on the main thread.
            Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Press 'c' to cancel within 3 seconds after work begins.");
                Console.WriteLine("Or let the task time out by doing nothing.");
                if (Console.ReadKey().KeyChar == 'c')
                    cts.Cancel();
            });

            // Let the user read the UI message.
            Thread.Sleep(1000);

            // Start the worker task.
            Task task = Task.Factory.StartNew(() => worker.DoWork(cts.Token), cts.Token);


            try
            {
                task.Wait(cts.Token);
            }

            catch (OperationCanceledException e)
            {
                if (e.CancellationToken == cts.Token)
                    Console.WriteLine("Canceled from UI thread throwing OCE.");
            }


            catch (AggregateException ae)
            {
                Console.WriteLine("AggregateException caught: " + ae.InnerException);
                foreach (var inner in ae.InnerExceptions)
                {
                    Console.WriteLine(inner.Message + inner.Source);
                }
            }

            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }

    class WorkerWithTimer
    {
        CancellationTokenSource internalTokenSource = new CancellationTokenSource();
        CancellationToken internalToken;
        CancellationToken externalToken;
        Timer timer;

        public WorkerWithTimer()
        {
            internalTokenSource = new CancellationTokenSource();
            internalToken = internalTokenSource.Token;

            // A toy cancellation trigger that times out after 3 seconds
            // if the user does not press 'c'.
            timer = new Timer(new TimerCallback(CancelAfterTimeout), null, 3000, 3000);
        }


        public void DoWork(CancellationToken externalToken)
        {
            // Create a new token that combines the internal and external tokens.
            this.internalToken = internalTokenSource.Token;
            this.externalToken = externalToken;

            using (CancellationTokenSource linkedCts =
                    CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken))
            {
                try
                {
                    DoWorkInternal(linkedCts.Token);
                }
                catch (OperationCanceledException)
                {
                    if (internalToken.IsCancellationRequested)
                    {
                        Console.WriteLine("Operation timed out.");
                    }
                    else if (externalToken.IsCancellationRequested)
                    {
                        Console.WriteLine("Cancelling per user request.");
                        externalToken.ThrowIfCancellationRequested();
                    }
                }
            }
        }


        private void DoWorkInternal(CancellationToken token)
        {
            for (int i = 0; i < 1000; i++)
            {
                if (token.IsCancellationRequested)
                {
                    // We need to dispose the timer if cancellation
                    // was requested by the external token.
                    timer.Dispose();

                    // Throw the exception.
                    token.ThrowIfCancellationRequested();
                }

                 // Simulating work.
                Thread.SpinWait(7500000);
                Console.Write("working... ");
            }
        }

        public void CancelAfterTimeout(object state)
        {
            Console.WriteLine("\r\nTimer fired.");
            internalTokenSource.Cancel();
            timer.Dispose();
        }
    }
}

当链接的标记引发 OperationCanceledException 时,传递给此异常的标记为该链接的标记,而不是任何一个前置标记。 若要确定取消哪些标记,请直接检查前置标记的状态。

在此示例中,应从不引发 AggregateException,但在此处捕获它是因为在现实情况中,从任务委托引发的除 OperationCanceledException 外的任何其他异常均包装在 OperationCanceledException 中。

请参见

概念

取消