Comment : écouter les demandes d'annulation avec des handles d'attente

Si une méthode est bloquée pendant qu'elle attend qu'un événement soit signalé, elle ne peut pas vérifier la valeur du jeton d'annulation et répondre en temps voulu. Le premier exemple montre comment résoudre ce problème lorsque vous travaillez avec des événements tels que System.Threading.ManualResetEvent qui ne prennent pas en charge l'infrastructure d'annulation unifiée en mode natif. Le deuxième exemple présente une approche plus simplifiée qui utilise System.Threading.ManualResetEventSlim, lequel prend en charge l'annulation unifiée.

RemarqueRemarque

Lorsque l'option "Uniquement mon code" est activée, Visual Studio s'arrête dans certains cas sur la ligne qui lève l'exception et affiche un message d'erreur indiquant que l'exception n'est pas gérée par le code utilisateur. Cette erreur est bénigne.Vous pouvez appuyer sur F5 pour continuer et voir le comportement de gestion des exceptions illustré dans les exemples ci-dessous.Pour empêcher Visual Studio de s'arrêter sur la première erreur, il vous suffit de désactiver la case à cocher "Uniquement mon code" sous Outils, Options, Débogage, Général.

Exemple

L'exemple suivant utilise un ManualResetEvent pour montrer comment débloquer des handles d'attente qui ne prennent pas en charge l'annulation unifiée.

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

L'exemple suivant utilise un ManualResetEventSlim pour montrer comment débloquer des primitives de coordination qui prennent en charge l'annulation unifiée. Les techniques décrites peuvent également être utilisées avec autres primitives de coordination simplifiées, telles que SemaphoreSlim et 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);
        }
    }
}

Voir aussi

Concepts

Annulation