方法: ポーリングによりキャンセル要求を待機する

次の例では、ユーザー コードでキャンセル トークンを一定の間隔でポーリングして、呼び出し元のスレッドからキャンセルが要求されたかどうかを確認する 1 つの方法を示します。 この例では System.Threading.Tasks.Task 型を使用しますが、System.Threading.ThreadPool 型または System.Threading.Thread 型によって直接作成された非同期操作にも同じパターンが適用されます。

使用例

ポーリングには、ブール型の IsCancellationRequested プロパティの値を定期的に読み取ることができる、ある種のループまたは再帰コードが必要です。 System.Threading.Tasks.Task 型を使用し、呼び出し元のスレッドでタスクが完了するのを待つ場合は、ThrowIfCancellationRequested メソッドを使用して、プロパティを確認し、例外をスローできます。 このメソッドを使用すると、要求への応答として正しい例外をスローすることができます。 Task を使用する場合は、OperationCanceledException を手動でスローするよりもこのメソッドを呼び出す方が良い方法です。 例外をスローする必要がない場合は、プロパティを確認し、プロパティが true のときはメソッドから制御を戻すだけです。

Class CancelByPolling

    Shared Sub Main()

        Dim tokenSource As New CancellationTokenSource()
        ' Toy object for demo purposes
        Dim rect As New Rectangle()
        rect.columns = 1000
        rect.rows = 500

        ' Simple cancellation scenario #1. Calling thread does not wait
        ' on the task to complete, and the user delegate simply returns
        ' on cancellation request without throwing.
        Task.Factory.StartNew(Sub() NestedLoops(rect, tokenSource.Token), tokenSource.Token)

        ' Simple cancellation scenario #2. Calling thread does not wait
        ' on the task to complete, and the user delegate throws 
        ' OperationCanceledException to shut down task and transition its state.
        ' Task.Factory.StartNew(Sub() PollByTimeSpan(tokenSource.Token), tokenSource.Token)

        Console.WriteLine("Press 'c' to cancel")
        If Console.ReadKey().KeyChar = "c"c Then

            tokenSource.Cancel()
            Console.WriteLine("Press any key to exit.")
        End If

        Console.ReadKey()

    End Sub
    Shared Sub NestedLoops(ByVal rect As Rectangle, ByVal token As CancellationToken)

        For x As Integer = 0 To rect.columns

            For y As Integer = 0 To rect.rows

                ' Simulating work.
                Thread.SpinWait(5000)
                Console.Write("0' end block,1' end block ", x, y)
            Next

            ' Assume that we know that the inner loop is very fast.
            ' Therefore, checking once per row is sufficient.
            If token.IsCancellationRequested = True Then

                ' Cleanup or undo here if necessary...
                Console.WriteLine("\r\nCancelling after row 0' end block.", x)
                Console.WriteLine("Press any key to exit.")
                ' then...
                Exit For
                ' ...or, if using Task:
                ' token.ThrowIfCancellationRequested()
            End If
        Next
    End Sub



End Class
class CancelByPolling
{
    static void Main()
    {
        var tokenSource = new CancellationTokenSource();
        // Toy object for demo purposes
        Rectangle rect = new Rectangle() { columns = 1000, rows = 500 };

        // Simple cancellation scenario #1. Calling thread does not wait
        // on the task to complete, and the user delegate simply returns
        // on cancellation request without throwing.
        Task.Factory.StartNew(() => NestedLoops(rect, tokenSource.Token), tokenSource.Token);

        // Simple cancellation scenario #2. Calling thread does not wait
        // on the task to complete, and the user delegate throws 
        // OperationCanceledException to shut down task and transition its state.
        // Task.Factory.StartNew(() => PollByTimeSpan(tokenSource.Token), tokenSource.Token);

        Console.WriteLine("Press 'c' to cancel");
        if (Console.ReadKey().KeyChar == 'c')
        {
            tokenSource.Cancel();
            Console.WriteLine("Press any key to exit.");
        }

        Console.ReadKey();

    }
    static void NestedLoops(Rectangle rect, CancellationToken token)
    {
        for (int x = 0; x < rect.columns && !token.IsCancellationRequested; x++)
        {
            for (int y = 0; y < rect.rows; y++)
            {
                // Simulating work.
                Thread.SpinWait(5000);
                Console.Write("{0},{1} ", x, y);
            }

            // Assume that we know that the inner loop is very fast.
            // Therefore, checking once per row is sufficient.
            if (token.IsCancellationRequested)
            {
                // Cleanup or undo here if necessary...
                Console.WriteLine("\r\nCancelling after row {0}.", x);
                Console.WriteLine("Press any key to exit.");
                // then...
                break;
                // ...or, if using Task:
                // token.ThrowIfCancellationRequested();
            }
        }
    }
}

ThrowIfCancellationRequested の呼び出しは非常に高速で、ループのオーバーヘッドが大きくなることはありません。

ThrowIfCancellationRequested を呼び出す場合、キャンセルへの応答として行う作業が例外のスロー以外にもあるときは、IsCancellationRequested プロパティを明示的に確認するだけで済みます。 この例では、実際にはコードからプロパティに 2 回アクセスすることがわかります。1 回は明示的なアクセスで、もう 1 回は ThrowIfCancellationRequested メソッドです。 ただし、IsCancellationRequested プロパティの読み取り操作にはアクセスごとに volatile の読み取り命令が 1 つあるだけなので、2 回のアクセスはパフォーマンスの観点からも特に問題ではありません。 やはり、OperationCanceledException を手動でスローするよりもこのメソッドを呼び出すことをお勧めします。

参照

その他の技術情報

キャンセル