Delen via


Procedure: Luisteren naar annuleringsaanvragen door polling

In het volgende voorbeeld ziet u een manier waarop gebruikerscode met regelmatige tussenpozen een annuleringstoken kan peilen om te zien of annulering is aangevraagd vanuit de aanroepende thread. In dit voorbeeld wordt het System.Threading.Tasks.Task type gebruikt, maar hetzelfde patroon is van toepassing op asynchrone bewerkingen die rechtstreeks door het System.Threading.ThreadPool type of het System.Threading.Thread type zijn gemaakt.

Opmerking

Polling vereist een soort lus of recursieve code die periodiek de waarde van de booleaanse IsCancellationRequested eigenschap kan lezen. Als u het System.Threading.Tasks.Task type gebruikt en u wacht tot de taak is voltooid in de aanroepende thread, kunt u de ThrowIfCancellationRequested methode gebruiken om de eigenschap te controleren en de uitzondering te genereren. Met deze methode zorgt u ervoor dat de juiste uitzondering wordt gegenereerd als reactie op een aanvraag. Als u een Task, dan is het aanroepen van deze methode beter dan handmatig een OperationCanceledException. Als u de uitzondering niet hoeft te genereren, kunt u gewoon de eigenschap controleren en terugkeren van de methode als de eigenschap 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 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

Bellen ThrowIfCancellationRequested is zeer snel en leidt niet tot aanzienlijke overhead in lussen.

Als u belt ThrowIfCancellationRequested, hoeft u de IsCancellationRequested eigenschap alleen expliciet te controleren als u ander werk moet doen als reactie op de annulering, naast het genereren van de uitzondering. In dit voorbeeld ziet u dat de code twee keer toegang heeft tot de eigenschap: eenmaal in de expliciete toegang en opnieuw in de ThrowIfCancellationRequested methode. Maar omdat het lezen van de IsCancellationRequested eigenschap slechts één vluchtige leesinstructie per toegang omvat, is de dubbele toegang niet significant vanuit prestatieperspectief. Het is nog steeds de voorkeur om de methode aan te roepen in plaats van handmatig de OperationCanceledException.

Zie ook