HOW TO:接聽具有等候控制代碼的取消要求

如果方法在等候事件接收信號時遭到封鎖,就無法檢查取消語彙基元的值並且即時回應。 在第一個範例中,會示範如何在您使用並非原生支援統一取消架構的事件 (例如 System.Threading.ManualResetEvent) 時解決這個問題。 第二個範例顯示更多使用 System.Threading.ManualResetEventSlim 的精簡方法,此方式支援統一取消。

注意事項注意事項

啟用 "Just My Code" 時,Visual Studio 有時候會在擲回例外狀況的程式碼行處中斷,並顯示「使用者程式碼未處理的例外狀況」之類的錯誤訊息。這是良性的錯誤。您可以按 F5 從中斷的地方繼續,並看到下面範例所示的例外處理行為。若要防止 Visual Studio 在遇到第一個錯誤時就中斷,只要取消選取 [工具]、[選項]、[偵錯]、[一般] 下的 [Just My Code] 核取方塊即可。

範例

下列範例會使用 ManualResetEvent,示範如何對不支援統一取消的等候控制代碼解除封鎖。

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

下列範例會使用 ManualResetEventSlim,示範如何對不支援統一取消的統整式基本項目解除封鎖。 相同的方法可以用於其他輕量型的統整式基本項目,例如 SemaphoreSlimCountdownEvent

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

請參閱

概念

取消