Condividi tramite


Procedura: mettersi in ascolto di richieste di annullamento con handle di attesa

Se un metodo è bloccato mentre è in attesa che un evento venga segnalato, non può controllare il valore del token di annullamento e rispondere in modo tempestivo. Nel primo esempio viene illustrato come risolvere questo problema quando si utilizzano eventi come System.Threading.ManualResetEvent che non supportano a livello nativo il framework di annullamento unificato. Nel secondo esempio viene illustrato un approccio più semplificato che prevede l'utilizzo di System.Threading.ManualResetEventSlim, che supporta l'annullamento unificato.

NotaNota

Quando viene abilitato "Just My Code", in alcuni casi Visual Studio si interromperà in corrispondenza della riga che genera l'eccezione e in cui viene visualizzato un messaggio di errore che indica che l'eccezione non è gestita dal codice utente. Questo errore è benigno.È possibile premere F5 per continuare e visualizzare il comportamento di gestione delle eccezioni illustrato negli esempi riportati di seguito.Per impedire l'interruzione di Visual Studio al primo errore, deselezionare semplicemente la casella di controllo "Just My Code" in Strumenti, Opzioni, Debug, Generale.

Esempio

Nell'esempio seguente viene utilizzato un oggetto ManualResetEvent per illustrare come sbloccare gli handle di attesa che non supportano l'annullamento unificato.

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

Nell'esempio riportato di seguito viene utilizzato un oggetto ManualResetEventSlim per illustrare come sbloccare le primitive di coordinamento che supportano l'annullamento unificato. Lo stesso approccio può essere utilizzato con le altre primitive di coordinamento leggere, ad esempio SemaphoreSlim e 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);
        }
    }
}

Vedere anche

Concetti

Annullamento