Procédure : écouter les requêtes d’annulation par interrogation

L’exemple suivant montre une manière dont un code utilisateur peut interroger un jeton d’annulation à intervalles réguliers pour voir si une annulation a été demandée à partir du thread appelant. Cet exemple utilise le type System.Threading.Tasks.Task, mais le même modèle s’applique aux opérations asynchrones créées directement par le type System.Threading.ThreadPool ou par le type System.Threading.Thread.

Exemple

L’interrogation requiert une boucle ou un code récursif qui peut lire régulièrement la valeur de la propriété booléenne IsCancellationRequested. Si vous utilisez le type System.Threading.Tasks.Task et que vous attendez que la tâche se termine sur le thread appelant, vous pouvez utiliser la méthode ThrowIfCancellationRequested pour vérifier la propriété et lever l’exception. En utilisant cette méthode, vous garantissez que la bonne exception est levée en réponse à une requête. Si vous utilisez un Task, il vaudra alors mieux appeler cette méthode plutôt que de lever manuellement un OperationCanceledException. Si vous ne devez pas lever l’exception, vous pouvez simplement vérifier la propriété et le retour de la méthode si la propriété est 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

L’appel de ThrowIfCancellationRequested est extrêmement rapide et n’introduit pas de surcharge importante dans les boucles.

Si vous appelez ThrowIfCancellationRequested, vous avez uniquement à vérifier explicitement la propriété IsCancellationRequested si, en plus de lever l’exception, vous avez un autre travail à faire en réponse à l’annulation. Dans cet exemple, vous pouvez voir qu’en fait, le code accède deux fois à la propriété : une fois dans l’accès explicite et une autre fois dans la méthode ThrowIfCancellationRequested. Cependant, étant donné que l’opération de lecture de la propriété IsCancellationRequested implique une seule instruction de lecture volatile par accès, le double accès n’est pas significatif du point de vue des performances. Il est toujours préférable d’appeler la méthode plutôt que de lever manuellement le OperationCanceledException.

Voir aussi