HOW TO:透過輪詢接聽取消要求

下列範例示範一種方式,讓使用者程式碼可以定期輪詢取消語彙基元,確認取消是否已透過呼叫執行緒提出。 這個範例會使用 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 屬性的動作只需用到一次 Volatile 讀取指令,因此存取兩次對於效能而言並不會產生很大的影響。 所以呼叫此方法仍然比手動擲回 OperationCanceledException 來得理想。

請參閱

概念

取消