Porady: nasłuchiwanie żądań anulowania za pomocą sondowania

Poniższy przykład przedstawia jeden ze sposobów sondowania tokenu anulowania przez kod użytkownika w regularnych odstępach czasu, aby sprawdzić, czy w wątku wywołującym zażądano anulowania. W tym przykładzie System.Threading.Tasks.Task użyto typu, ale ten sam wzorzec dotyczy operacji asynchronicznych utworzonych bezpośrednio przez System.Threading.ThreadPool typ lub System.Threading.Thread typ.

Przykład

Sondowanie wymaga pewnego rodzaju pętli lub cyklicznego kodu, który może okresowo odczytywać wartość właściwości logicznej IsCancellationRequested . Jeśli używasz System.Threading.Tasks.Task typu i czekasz na ukończenie zadania w wątku wywołującym, możesz użyć ThrowIfCancellationRequested metody , aby sprawdzić właściwość i zgłosić wyjątek. Korzystając z tej metody, upewnij się, że prawidłowy wyjątek jest zgłaszany w odpowiedzi na żądanie. Jeśli używasz Taskmetody , wywołanie tej metody jest lepsze niż ręczne zgłaszanie elementu OperationCanceledException. Jeśli nie musisz zgłaszać wyjątku, możesz po prostu sprawdzić właściwość i wrócić z metody , jeśli właściwość ma truewartość .

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 col = 0; col < rect.columns && !token.IsCancellationRequested; col++) {
         // Assume that we know that the inner loop is very fast.
         // Therefore, polling once per column in the outer loop condition
         // is sufficient.
         for (int row = 0; row < rect.rows; row++) {
            // Simulating work.
            Thread.SpinWait(5_000);
            Console.Write("{0},{1} ", col, row);
         }
      }

      if (token.IsCancellationRequested) {
         // Cleanup or undo here if necessary...
         Console.WriteLine("\r\nOperation canceled");
         Console.WriteLine("Press any key to exit.");

         // 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)
        Dim col As Integer
        For col = 0 To rect.columns - 1
            ' Assume that we know that the inner loop is very fast.
            ' Therefore, polling once per column in the outer loop condition
            ' is sufficient.
            For col As Integer = 0 To rect.rows - 1
                ' Simulating work.
                Thread.SpinWait(5000)
                Console.Write("0',1' ", x, y)
            Next
        Next

        If token.IsCancellationRequested = True Then
            ' Cleanup or undo here if necessary...
            Console.WriteLine(vbCrLf + "Operation canceled")
            Console.WriteLine("Press any key to exit.")

            ' If using Task:
            ' token.ThrowIfCancellationRequested()
        End If
    End Sub
End Class

Wywoływanie ThrowIfCancellationRequested jest bardzo szybkie i nie wprowadza znaczących narzutów w pętlach.

Jeśli wywołujesz metodę ThrowIfCancellationRequested, musisz jawnie sprawdzić IsCancellationRequested właściwość, jeśli masz inne czynności, które należy wykonać w odpowiedzi na anulowanie, oprócz zgłaszania wyjątku. W tym przykładzie widać, że kod dwukrotnie uzyskuje dostęp do właściwości: raz w jawnym dostępie i ponownie w metodzie ThrowIfCancellationRequested . Ale ponieważ czynność odczytu IsCancellationRequested właściwości obejmuje tylko jedną nietrwałą instrukcję odczytu na dostęp, podwójny dostęp nie jest znaczący z punktu widzenia wydajności. Nadal zaleca się wywołanie metody, a nie ręcznego wyrzucenia OperationCanceledExceptionmetody .

Zobacz też