Compartir a través de


Cómo: Realizar escuchas de varias solicitudes de cancelación

En este ejemplo se muestra cómo escuchar dos tokens de cancelación simultáneamente de manera que pueda cancelar una operación si uno de los tokens lo solicita.

NotaNota

Cuando está habilitada la opción "Solo mi código", en algunos casos, Visual Studio se interrumpe en la línea que produce la excepción y muestra el mensaje de error "Excepción no controlada por el código de usuario". Este error es benigno.Puede presionar F5 para continuar y ver el comportamiento de control de excepciones que se muestra en los ejemplos siguientes.Para evitar que Visual Studio se interrumpa con el primer error, desactive la casilla "Solo mi código" bajo Herramientas, Opciones, Depuración, General.

Ejemplo

En el ejemplo siguiente se usa el método [M:M:System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken[])] para combinar dos tokens en uno. Esto permite pasar el token a métodos que solo toman un token de cancelación como argumento. En el ejemplo se muestra un escenario común en el que un método debe observar un token pasado desde fuera de la clase y un token generado dentro de la clase.

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();
        }
    }
}

Cuando el token vinculado produce OperationCanceledException, el token que se pasa a la excepción es el token vinculado, no uno de los token del predecesor. Para determinar cuál de los tokens se canceló, compruebe el estado de los tokens del predecesor directamente.

En este ejemplo, AggregateException no se debe producir nunca, pero se detecta aquí porque en los escenarios reales todas las demás excepciones aparte de OperationCanceledException que se producen desde el delegado de la tarea se encapsulan en una excepción OperationCanceledException.

Vea también

Conceptos

Cancelación