方法: 待機ハンドルがあるキャンセル要求を待機する

メソッドは、イベントの通知を待機しているときにブロックされると、キャンセル トークンの値を確認できず、適時に応答することもできません。 最初の例では、統合キャンセル フレームワークをネイティブにはサポートしない System.Threading.ManualResetEvent などのイベントを使用している場合にこの問題を解決する方法を示します。 2 番目の例では、統合キャンセルをサポートする System.Threading.ManualResetEventSlim を使用した効率的な方法を示します。

メモメモ

[マイ コードのみ] が有効になっている場合、Visual Studio では、例外をスローする行で処理が中断され、"ユーザー コードで処理されない例外" に関するエラー メッセージが表示されることがあります。 このエラーは問題にはなりません。F5 キーを押して、処理が中断された箇所から続行し、以下の例に示す例外処理動作を確認できます。Visual Studio による処理が最初のエラーで中断しないようにするには、[ツール] メニューの [オプション]、[デバッグ] の順にクリックし、[全般] で [マイ コードのみ] チェック ボックスをオフにします。

使用例

次の例では、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);
        }
    }
}

参照

その他の技術情報

キャンセル