如何:通过轮询侦听取消请求

下面的示例演示一种方法,通过该方法用户代码可以定期轮询取消标记以查看是否已从调用线程请求取消。 此示例使用 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 属性即可。 在此示例中,可以发现代码实际上访问该属性两次:一次是在显式访问中,另一次是在 ThrowIfCancellationRequested 方法中。 但是,由于读取 IsCancellationRequested 属性的操作针对每次访问只包含一条可变读取指令,两次访问不会对性能有太大影响。 调用该方法仍优于手动引发 OperationCanceledException

请参见

概念

取消