Compartir a través de


Cómo: Realizar escuchas de solicitudes de cancelación cuando tienen controladores de espera

Si se bloquea un método mientras está esperando la señalización de un evento, no puede comprobar el valor del token de cancelación y responder a tiempo. En el primer ejemplo se muestra cómo resolver este problema cuando está trabajando con eventos como System.Threading.ManualResetEvent que no admiten el marco de cancelación unificada de forma nativa. En el segundo ejemplo se muestra un enfoque más sencillo que usa System.Threading.ManualResetEventSlim, que admite cancelación unificada.

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 ManualResetEvent para mostrar cómo desbloquear identificadores de espera que no admiten cancelación unificada.

' Imports System
' Imports System.Threading
' Imports System.Threading.Tasks

Class CancelOldStyleEvents

    ' Old-style MRE that doesn't support unified cancellation.
    Shared mre As New ManualResetEvent(False)

    Shared Sub Main()

        Dim cts As New CancellationTokenSource()

        ' Pass the same token source to the delegate and to the task instance.
        Task.Factory.StartNew(Sub() DoWork(cts.Token), cts.Token)
        Console.WriteLine("Press c to cancel, p to pause, or s to start/restart.")
        Console.WriteLine("Or any other key to exit.")

        ' Old-style UI thread.
        Dim goAgain As Boolean = True
        While goAgain = True

            Dim ch As Char = Console.ReadKey().KeyChar

            Select Case ch
                Case "c"c
                    cts.Cancel()

                Case "p"c
                    mre.Reset()

                Case "s"c
                    mre.Set()

                Case Else
                    goAgain = False

            End Select

            Thread.Sleep(100)
        End While
    End Sub


    Shared Sub DoWork(ByVal token As CancellationToken)

        While True

            ' Wait on the event if it is not signaled.
            Dim myWaitHandle(2) As WaitHandle
            myWaitHandle(0) = mre
            myWaitHandle(1) = token.WaitHandle
            Dim eventThatSignaledIndex =
                WaitHandle.WaitAny(myWaitHandle, _
                                    New TimeSpan(0, 0, 20))

            ' Were we canceled while waiting?
            ' The first If statement is equivalent to 
            ' token.ThrowIfCancellationRequested()
            If eventThatSignaledIndex = 1 Then

                Console.WriteLine("The wait operation was canceled.")
                Throw New OperationCanceledException(token)

                ' Were we canceled while running?
            ElseIf token.IsCancellationRequested = True Then

                Console.WriteLine("Cancelling per user request.")
                token.ThrowIfCancellationRequested()


                ' Did we time out?
            ElseIf eventThatSignaledIndex = WaitHandle.WaitTimeout Then

                Console.WriteLine("The wait operation timed out.")
                Exit While

            Else

                ' Simulating work.
                Console.Write("Working... ")
                Thread.SpinWait(5000000)
            End If
        End While
    End Sub
End Class
using System;
using System.Threading;
using System.Threading.Tasks;

class CancelOldStyleEvents
{
    // Old-style MRE that doesn't support unified cancellation.
    static ManualResetEvent mre = new ManualResetEvent(false);

    static void Main()
    {
        var cts = new CancellationTokenSource();

        // Pass the same token source to the delegate and to the task instance.
        Task.Factory.StartNew(() => DoWork(cts.Token), cts.Token);
        Console.WriteLine("Press s to start/restart, p to pause, or c to cancel.");
        Console.WriteLine("Or any other key to exit.");

        // Old-style UI thread.
        bool goAgain = true;
        while (goAgain)
        {
            char ch = Console.ReadKey().KeyChar;

            switch (ch)
            {
                case 'c':
                    cts.Cancel();
                    break;
                case 'p':
                    mre.Reset();
                    break;
                case 's':
                    mre.Set();
                    break;
                default:
                    goAgain = false;
                    break;
            }

            Thread.Sleep(100);
        }
    }

    static void DoWork(CancellationToken token)
    {
        while (true)
        {
            // Wait on the event if it is not signaled.
            int eventThatSignaledIndex =
                WaitHandle.WaitAny(new WaitHandle[] { mre, token.WaitHandle },
                                    new TimeSpan(0, 0, 20));

            // Were we canceled while waiting?
            if (eventThatSignaledIndex == 1)
            {
                Console.WriteLine("The wait operation was canceled.");
                throw new OperationCanceledException(token);

            }
            // Were we canceled while running?
            else if (token.IsCancellationRequested)
            {
                Console.WriteLine("I was canceled while running.");
                token.ThrowIfCancellationRequested();

            }
            // Did we time out?
            else if (eventThatSignaledIndex == WaitHandle.WaitTimeout)
            {
                Console.WriteLine("I timed out.");
                break;
            }
            else
            {
                Console.Write("Working... ");
                // Simulating work.
                Thread.SpinWait(5000000);
            }
        }
    }
}

En el ejemplo siguiente se usa ManualResetEventSlim para mostrar cómo desbloquear primitivas de coordinación que admiten cancelación unificada. Se puede usar el mismo enfoque con otras primitivas de coordinación ligeras, como SemaphoreSlim y CountdownEvent.

Class CancelNewStyleEvents

    ' New-style MRESlim that supports unified cancellation
    ' in its Wait methods.
    Shared mres As ManualResetEventSlim = New ManualResetEventSlim(False)

    Shared Sub Main()

        Dim cts As New CancellationTokenSource()

        ' Pass the same token source to the delegate and to the task instance.
        Task.Factory.StartNew(Sub() DoWork(cts.Token), cts.Token)
        Console.WriteLine("Press c to cancel, p to pause, or s to start/restart,")
        Console.WriteLine("or any other key to exit.")

        ' New-style UI thread.
        Dim goAgain As Boolean = True
        While goAgain = True

            Dim ch As Char = Console.ReadKey().KeyChar

            Select Case ch

                Case "c"c
                    ' Token can only be canceled once.
                    cts.Cancel()

                Case "p"c
                    mres.Reset()

                Case "s"c
                    mres.Set()

                Case Else
                    goAgain = False

            End Select

            Thread.Sleep(100)
        End While

    End Sub

    Shared Sub DoWork(ByVal token As CancellationToken)


        While True

            If token.IsCancellationRequested Then

                Console.WriteLine("Canceled while running.")
                token.ThrowIfCancellationRequested()
            End If

            ' Wait on the event to be signaled 
            ' or the token to be canceled,
            ' whichever comes first. The token
            ' will throw an exception if it is canceled
            ' while the thread is waiting on the event.
            Try

            ' mres is a ManualResetEventSlim
              mres.Wait(token)
            Catch e As OperationCanceledException

                ' Throw immediately to be responsive. The
                ' alternative is to do one more item of work,
                ' and throw on next iteration, because 
                ' IsCancellationRequested will be true.
                Console.WriteLine("Canceled while waiting.")
                Throw
            End Try

             ' Simulating work.
            Console.Write("Working...")
            Thread.SpinWait(500000)
        End While
    End Sub
End Class
class CancelNewStyleEvents
{
    // New-style MRESlim that supports unified cancellation
    // in its Wait methods.
    static ManualResetEventSlim mres = new ManualResetEventSlim(false);

    static void Main()
    {
        var cts = new CancellationTokenSource();

        // Pass the same token source to the delegate and to the task instance.
        Task.Factory.StartNew(() => DoWork(cts.Token), cts.Token);
        Console.WriteLine("Press c to cancel, p to pause, or s to start/restart,");
        Console.WriteLine("or any other key to exit.");

        // New-style UI thread.
        bool goAgain = true;
        while (goAgain)
        {
            char ch = Console.ReadKey().KeyChar;

            switch (ch)
            {
                case 'c':
                    // Token can only be canceled once.
                    cts.Cancel();
                    break;
                case 'p':
                    mres.Reset();
                    break;
                case 's':
                    mres.Set();
                    break;
                default:
                    goAgain = false;
                    break;
            }

            Thread.Sleep(100);
        }

    }

    static void DoWork(CancellationToken token)
    {

        while (true)
        {
            if (token.IsCancellationRequested)
            {
                Console.WriteLine("Canceled while running.");
                token.ThrowIfCancellationRequested();
            }

            // Wait on the event to be signaled 
            // or the token to be canceled,
            // whichever comes first. The token
            // will throw an exception if it is canceled
            // while the thread is waiting on the event.
            try
            {
                // mres is a ManualResetEventSlim
                mres.Wait(token);
            }
            catch (OperationCanceledException)
            {
                // Throw immediately to be responsive. The
                // alternative is to do one more item of work,
                // and throw on next iteration, because 
                // IsCancellationRequested will be true.
                Console.WriteLine("The wait operation was canceled.");
                throw;
            }

            Console.Write("Working...");
            // Simulating work.
            Thread.SpinWait(500000);
        }
    }
}

Vea también

Conceptos

Cancelación