Partager via


Comment : annuler une requête PLINQ

Les exemples suivants montrent deux façons d'annuler une requête PLINQ. Le premier exemple indique comment annuler une requête qui se compose principalement d'un parcours de données. Le deuxième exemple indique comment annuler une requête qui contient une fonction de l'utilisateur sollicitant fortement les ressources informatiques.

RemarqueRemarque

Lorsque l'option "Uniquement mon code" est activée, Visual Studio s'arrête sur la ligne qui lève l'exception et affiche un message d'erreur indiquant que l'exception n'est pas gérée par le code utilisateur. Cette erreur est bénigne.Vous pouvez appuyer sur F5 pour continuer et voir le comportement de gestion des exceptions illustré dans les exemples ci-dessous.Pour empêcher Visual Studio de s'arrêter sur la première erreur, il vous suffit de désactiver la case à cocher "Uniquement mon code" sous Outils, Options, Débogage, Général.

Cet exemple est destiné à montrer l'utilisation et peut ne pas s'exécuter plus rapidement que la requête LINQ to Objects séquentielle équivalente.Pour plus d'informations sur l'accélération, consultez Fonctionnement de l'accélération dans PLINQ.

Exemple

Class Program
    Private Shared Sub Main(ByVal args As String())
        Dim source As Integer() = Enumerable.Range(1, 10000000).ToArray()
        Dim cs As New CancellationTokenSource()

        ' Start a new asynchronous task that will cancel the 
        ' operation from another thread. Typically you would call
        ' Cancel() in response to a button click or some other
        ' user interface event.
        Task.Factory.StartNew(Sub()
                                  UserClicksTheCancelButton(cs)
                              End Sub)

        Dim results As Integer() = Nothing
        Try

            results = (From num In source.AsParallel().WithCancellation(cs.Token) _
                Where num Mod 3 = 0 _
                Order By num Descending _
                Select num).ToArray()
        Catch e As OperationCanceledException

            Console.WriteLine(e.Message)
        Catch ae As AggregateException

            If ae.InnerExceptions IsNot Nothing Then
                For Each e As Exception In ae.InnerExceptions
                    Console.WriteLine(e.Message)
                Next
            End If
        End Try

        If results IsNot Nothing Then
            For Each item In results
                Console.WriteLine(item)
            Next
        End If
        Console.WriteLine()

        Console.ReadKey()
    End Sub

    Private Shared Sub UserClicksTheCancelButton(ByVal cs As CancellationTokenSource)
        ' Wait between 150 and 500 ms, then cancel.
        ' Adjust these values if necessary to make
        ' cancellation fire while query is still executing.
        Dim rand As New Random()
        Thread.Sleep(rand.[Next](150, 350))
        cs.Cancel()
    End Sub
End Class
namespace PLINQCancellation_1
{
    using System;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {
            int[] source = Enumerable.Range(1, 10000000).ToArray();
            CancellationTokenSource cs = new CancellationTokenSource();

            // Start a new asynchronous task that will cancel the 
            // operation from another thread. Typically you would call
            // Cancel() in response to a button click or some other
            // user interface event.
            Task.Factory.StartNew(() =>
            {
                UserClicksTheCancelButton(cs);
            });

            int[] results = null;
            try
            {
                results = (from num in source.AsParallel().WithCancellation(cs.Token)
                           where num % 3 == 0
                           orderby num descending
                           select num).ToArray();

            }

            catch (OperationCanceledException e)
            {
                Console.WriteLine(e.Message);
            }

            catch (AggregateException ae)
            {
                if (ae.InnerExceptions != null)
                {
                    foreach (Exception e in ae.InnerExceptions)
                        Console.WriteLine(e.Message);
                }
            }

            if (results != null)
            {
                foreach (var v in results)
                    Console.WriteLine(v);
            }
            Console.WriteLine();
            Console.ReadKey();

        }

        static void UserClicksTheCancelButton(CancellationTokenSource cs)
        {
            // Wait between 150 and 500 ms, then cancel.
            // Adjust these values if necessary to make
            // cancellation fire while query is still executing.
            Random rand = new Random();
            Thread.Sleep(rand.Next(150, 350));
            cs.Cancel();
        }
    }
}

L'infrastructure PLINQ ne restaure pas une OperationCanceledException unique en une System.AggregateException ; OperationCanceledException doit être gérée dans un bloc catch séparé. Si un ou plusieurs délégués utilisateurs lèvent un OperationCanceledException(externalCT) (en utilisant un System.Threading.CancellationToken externe) et aucune autre exception, et que la requête a été définie en tant que AsParallel().WithCancellation(externalCT), PLINQ émettra une OperationCanceledException unique (externalCT) plutôt qu'une System.AggregateException. Toutefois, si un délégué utilisateur lève un OperationCanceledException et un autre délégué lève un autre type d'exception, les deux exceptions seront restaurées en AggregateException.

La recommandation générale sur l'annulation est la suivante :

  1. Si vous exécutez une annulation de délégué utilisateur, vous devez informer PLINQ du CancellationToken externe et lever une OperationCanceledException(externalCT).

  2. Si l'annulation se produit et qu'aucune autre exception n'est levée, puis, vous devez gérer une OperationCanceledException plutôt qu'une AggregateException.

L'exemple suivant indique comment gérer une annulation lorsqu'une fonction du code utilisateur sollicite fortement les ressources informatiques.

Class Program2
    Private Shared Sub Main(ByVal args As String())


        Dim source As Integer() = Enumerable.Range(1, 10000000).ToArray()
        Dim cs As New CancellationTokenSource()

        ' Start a new asynchronous task that will cancel the 
        ' operation from another thread. Typically you would call
        ' Cancel() in response to a button click or some other
        ' user interface event.
        Task.Factory.StartNew(Sub()

                                  UserClicksTheCancelButton(cs)
                              End Sub)

        Dim results As Double() = Nothing
        Try

            results = (From num In source.AsParallel().WithCancellation(cs.Token) _
                Where num Mod 3 = 0 _
                Select [Function](num, cs.Token)).ToArray()
        Catch e As OperationCanceledException


            Console.WriteLine(e.Message)
        Catch ae As AggregateException
            If ae.InnerExceptions IsNot Nothing Then
                For Each e As Exception In ae.InnerExceptions
                    Console.WriteLine(e.Message)
                Next
            End If
        End Try

        If results IsNot Nothing Then
            For Each item In results
                Console.WriteLine(item)
            Next
        End If
        Console.WriteLine()

        Console.ReadKey()
    End Sub

    ' A toy method to simulate work.
    Private Shared Function [Function](ByVal n As Integer, ByVal ct As CancellationToken) As Double
        ' If work is expected to take longer than 1 ms
        ' then try to check cancellation status more
        ' often within that work.
        For i As Integer = 0 To 4
            ' Work hard for approx 1 millisecond.
            Thread.SpinWait(50000)

            ' Check for cancellation request.
            If ct.IsCancellationRequested Then
                Throw New OperationCanceledException(ct)
            End If
        Next
        ' Anything will do for our purposes.
        Return Math.Sqrt(n)
    End Function

    Private Shared Sub UserClicksTheCancelButton(ByVal cs As CancellationTokenSource)
        ' Wait between 150 and 500 ms, then cancel.
        ' Adjust these values if necessary to make
        ' cancellation fire while query is still executing.
        Dim rand As New Random()
        Thread.Sleep(rand.[Next](150, 350))
        Console.WriteLine("Press 'c' to cancel")
        If Console.ReadKey().KeyChar = "c"c Then
            cs.Cancel()

        End If
    End Sub
End Class
namespace PLINQCancellation_2
{
    using System;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {


            int[] source = Enumerable.Range(1, 10000000).ToArray();
            CancellationTokenSource cs = new CancellationTokenSource();

            // Start a new asynchronous task that will cancel the 
            // operation from another thread. Typically you would call
            // Cancel() in response to a button click or some other
            // user interface event.
            Task.Factory.StartNew(() =>
            {
                UserClicksTheCancelButton(cs);
            });

            double[] results = null;
            try
            {
                results = (from num in source.AsParallel().WithCancellation(cs.Token)
                           where num % 3 == 0
                           select Function(num, cs.Token)).ToArray();

            }


            catch (OperationCanceledException e)
            {
                Console.WriteLine(e.Message);
            }
            catch (AggregateException ae)
            {
                if (ae.InnerExceptions != null)
                {
                    foreach (Exception e in ae.InnerExceptions)
                        Console.WriteLine(e.Message);
                }
            }

            if (results != null)
            {
                foreach (var v in results)
                    Console.WriteLine(v);
            }
            Console.WriteLine();
            Console.ReadKey();

        }

        // A toy method to simulate work.
        static double Function(int n, CancellationToken ct)
        {
            // If work is expected to take longer than 1 ms
            // then try to check cancellation status more
            // often within that work.
            for (int i = 0; i < 5; i++)
            {
                // Work hard for approx 1 millisecond.
                Thread.SpinWait(50000);

                // Check for cancellation request.
                ct.ThrowIfCancellationRequested();
            }
            // Anything will do for our purposes.
            return Math.Sqrt(n);
        }

        static void UserClicksTheCancelButton(CancellationTokenSource cs)
        {
            // Wait between 150 and 500 ms, then cancel.
            // Adjust these values if necessary to make
            // cancellation fire while query is still executing.
            Random rand = new Random();
            Thread.Sleep(rand.Next(150, 350));
            Console.WriteLine("Press 'c' to cancel");
            if (Console.ReadKey().KeyChar == 'c')
                cs.Cancel();
        }
    }
}

Lorsque vous gérez une annulation dans du code utilisateur, vous n'avez pas à utiliser WithCancellation<TSource> dans la définition de la requête. Toutefois, ceci est recommandé car WithCancellation<TSource> n'a aucun effet sur les performances des requêtes et permet la gestion de l'annulation par les opérateurs de requête et votre code utilisateur.

Pour vérifier la réactivité du système, il est recommandé de vérifier les annulations toutes les millisecondes ; toutefois, toute durée inférieure ou égale à 10 millisecondes est considérée comme acceptable. Cette fréquence ne doit pas avoir d'impact négatif sur les performances de votre code.

Lorsqu'un énumérateur est supprimé, par exemple lorsque le code quitte une boucle foreach (For Each en Visual Basic) qui itère au sein de résultats de requête, la requête est annulée, mais aucune exception n'est levée.

Voir aussi

Référence

ParallelEnumerable

Concepts

Parallel LINQ (PLINQ)

Annulation

Historique des modifications

Date

Historique

Motif

Mai 2010

Remarque ajoutée concernant l'utilisation et l'accélération.

Commentaires client.