Megosztás a következőn keresztül:


Útmutató: PLINQ-lekérdezés megszakítása

Az alábbi példák két módszert mutatnak be a PLINQ-lekérdezések megszakítására. Az első példa bemutatja, hogyan lehet megszakítani egy olyan lekérdezést, amely többnyire adatbejárásból áll. A második példa bemutatja, hogyan szakíthat meg egy számításilag költséges felhasználói függvényt tartalmazó lekérdezést.

Feljegyzés

Ha a "Just My Code" engedélyezve van, a Visual Studio megtörik a kivételt jelző sorban, és megjelenik egy hibaüzenet, amely azt jelzi, hogy "a felhasználói kód által nem kezelt kivétel". Ez a hiba jóindulatú. A folytatáshoz nyomja le az F5 billentyűt, és tekintse meg az alábbi példákban bemutatott kivételkezelési viselkedést. Ha meg szeretné akadályozni, hogy a Visual Studio feltörje az első hibát, törölje a jelet a "Just My Code" (Csak saját kód) jelölőnégyzetből az Eszközök, Beállítások, Hibakeresés, Általános területen.

Ez a példa a használat bemutatására szolgál, és előfordulhat, hogy nem fut gyorsabban, mint az objektumokhoz tartozó, szekvenciális LINQ lekérdezés. További információ a gyorsításról: Understanding Speedup in PLINQ.

1. példa

namespace PLINQCancellation_1
{
    using System;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using static System.Console;

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

            // 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(cts);
            });

            int[]? results = null;
            try
            {
                results =
                    (from num in source.AsParallel().WithCancellation(cts.Token)
                     where num % 3 == 0
                     orderby num descending
                     select num).ToArray();
            }
            catch (OperationCanceledException e)
            {
                WriteLine(e.Message);
            }
            catch (AggregateException ae)
            {
                if (ae.InnerExceptions != null)
                {
                    foreach (Exception e in ae.InnerExceptions)
                    {
                        WriteLine(e.Message);
                    }
                }
            }

            foreach (var item in results ?? Array.Empty<int>())
            {
                WriteLine(item);
            }
            WriteLine();
            ReadKey();
        }

        static void UserClicksTheCancelButton(CancellationTokenSource cts)
        {
            // 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();
            Thread.Sleep(rand.Next(150, 500));
            cts.Cancel();
        }
    }
}
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
        Finally
            cs.Dispose()
        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

A PLINQ-keretrendszer egyetlen műveletet sem helyez OperationCanceledException egybe System.AggregateException; azokat OperationCanceledException külön fogási blokkban kell kezelni. Ha egy vagy több felhasználómegbízott egy OperationCanceledException(externalCT) parancsot küld (külső System.Threading.CancellationTokenhasználatával), de nincs más kivétel, és a lekérdezés a következőképpen AsParallel().WithCancellation(externalCT)lett definiálva, akkor a PLINQ egyetlen OperationCanceledException (externalCT) fájlt ad ki egy System.AggregateExceptionhelyett. Ha azonban egy felhasználómegbízott egy másik kivételtípust ad OperationCanceledExceptionmeg, akkor mindkét kivételt AggregateExceptionegy .

A lemondással kapcsolatos általános útmutatás a következő:

  1. Ha felhasználó-delegált lemondást hajt végre, tájékoztassa a PLINQ-t a külsőről CancellationToken , és dobjon egy OperationCanceledException(externalCT) parancsot.

  2. Ha a lemondás történik, és nincs más kivétel, akkor kezelje OperationCanceledException ahelyett, hogy egy AggregateException.

2. példa

Az alábbi példa bemutatja, hogyan kezelheti a lemondást, ha számításilag költséges függvény van a felhasználói kódban.

namespace PLINQCancellation_2
{
    using System;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using static System.Console;

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

            // 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(cts);
            });

            double[]? results = null;
            try
            {
                results =
                    (from num in source.AsParallel().WithCancellation(cts.Token)
                     where num % 3 == 0
                     select Function(num, cts.Token)).ToArray();
            }
            catch (OperationCanceledException e)
            {
                WriteLine(e.Message);
            }
            catch (AggregateException ae)
            {
                if (ae.InnerExceptions != null)
                {
                    foreach (Exception e in ae.InnerExceptions)
                        WriteLine(e.Message);
                }
            }

            foreach (var item in results ?? Array.Empty<double>())
            {
                WriteLine(item);
            }
            WriteLine();
            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 cts)
        {
            // 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();
            Thread.Sleep(rand.Next(150, 500));
            WriteLine("Press 'c' to cancel");
            if (ReadKey().KeyChar == 'c')
            {
                cts.Cancel();
            }
        }
    }
}
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
        Finally
            cs.Dispose()
        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

Ha a felhasználói kódban kezeli a lemondást, nem kell használnia WithCancellation a lekérdezésdefinícióban. Javasoljuk azonban, hogy használja WithCancellation, mert WithCancellation nincs hatással a lekérdezési teljesítményre, és lehetővé teszi, hogy a törlést a lekérdezési operátorok és a felhasználói kód kezelje.

A rendszer válaszkészségének biztosítása érdekében javasoljuk, hogy ezredmásodpercenként körülbelül egyszer ellenőrizze a lemondást; 10 ezredmásodpercig terjedő időszak azonban elfogadhatónak tekinthető. Ez a gyakoriság nem befolyásolhatja negatívan a kód teljesítményét.

Ha egy enumerátor el van adva, például amikor a kód egy foreach (Mindegyik a Visual Basicben) ciklusból tör ki, amely a lekérdezés eredményein iterál, a lekérdezés megszakad, de kivétel nem történik.

Lásd még