如何:透過輪詢接聽取消要求How to: Listen for Cancellation Requests by Polling

下列範例將示範一種方式:使用者程式碼可定期輪詢取消語彙基元,以查看呼叫執行緒是否已要求取消。The following example shows one way that user code can poll a cancellation token at regular intervals to see whether cancellation has been requested from the calling thread. 這個範例會使用 System.Threading.Tasks.Task 類型,但相同的模式適用於 System.Threading.ThreadPool 類型或 System.Threading.Thread 類型直接建立的非同步作業。This example uses the System.Threading.Tasks.Task type, but the same pattern applies to asynchronous operations created directly by the System.Threading.ThreadPool type or the System.Threading.Thread type.

範例Example

輪詢需要某種可定期讀取布林值 IsCancellationRequested 屬性值的迴圈或遞迴程式碼。Polling requires some kind of loop or recursive code that can periodically read the value of the Boolean IsCancellationRequested property. 如果您使用 System.Threading.Tasks.Task 類型且正在等候呼叫執行緒上的工作完成,您可以使用 ThrowIfCancellationRequested 方法來檢查屬性,並擲回例外狀況。If you are using the System.Threading.Tasks.Task type and you are waiting for the task to complete on the calling thread, you can use the ThrowIfCancellationRequested method to check the property and throw the exception. 使用此方法,您可以確保會擲回正確的例外狀況來回應要求。By using this method, you ensure that the correct exception is thrown in response to a request. 如果您使用 Task,則呼叫此方法優於手動擲回 OperationCanceledExceptionIf you are using a Task, then calling this method is better than manually throwing an OperationCanceledException. 如果您不需擲回例外狀況,則只需檢查屬性,如果屬性為 true,即從方法返回。If you do not have to throw the exception, then you can just check the property and return from the method if the property is true.

using System;
using System.Threading;
using System.Threading.Tasks;

public struct Rectangle
{
   public int columns;
   public int rows;
}

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.Run(() => 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.Run(() => PollByTimeSpan(tokenSource.Token), tokenSource.Token);

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

      Console.ReadKey();
      tokenSource.Dispose();
  }

   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();
         }
      }
   }
}
Imports System.Threading
Imports System.Threading.Tasks

Public Structure Rectangle
    Public columns As Integer
    Public rows As Integer
End Structure

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.Run(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.Run(Sub() PollByTimeSpan(tokenSource.Token), tokenSource.Token)

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

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

        Console.ReadKey()
        tokenSource.Dispose()
    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(vbCrLf + "Cancelling 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

呼叫 ThrowIfCancellationRequested 相當快速,不會在迴圈中引進大量額外負荷。Calling ThrowIfCancellationRequested is extremely fast and does not introduce significant overhead in loops.

如果您正在呼叫 ThrowIfCancellationRequested,當您要進行其他工作來回應取消 (擲回例外狀況除外) 時,只需明確檢查 IsCancellationRequested 屬性。If you are calling ThrowIfCancellationRequested, you only have to explicitly check the IsCancellationRequested property if you have other work to do in response to the cancellation besides throwing the exception. 在此範例中,您可以看到程式碼會實際存取屬性兩次:一次是明確的存取,另一次則是在 ThrowIfCancellationRequested 方法中。In this example, you can see that the code actually accesses the property twice: once in the explicit access and again in the ThrowIfCancellationRequested method. 但由於讀取 IsCancellationRequested 屬性的動作牽涉到每次存取只能有一個暫時性讀取指令,因此,從效能觀點來看,兩次存取並不重要。But because the act of reading the IsCancellationRequested property involves only one volatile read instruction per access, the double access is not significant from a performance perspective. 最好還是呼叫方法,而非手動擲回 OperationCanceledExceptionIt is still preferable to call the method rather than manually throw the OperationCanceledException.

另請參閱See also