Поделиться через


Практическое руководство. Отмена запроса PLINQ

В следующих примерах показано два способа отмены запроса PLINQ. В первом примере показано, как отменить запрос, состоящий в основном из обхода данных. Во втором примере показано, как отменить запрос, который содержит пользовательскую функцию, требующую больших затрат компьютерных ресурсов.

ПримечаниеПримечание

Если включен режим "Только мой код", Visual Studio прервет выполнение программы на строке, в которой создается исключение, и отобразит сообщение об ошибке "Исключение, которое не может быть обработано пользовательским кодом". Эта ошибка не является критической.Можно нажать F5, чтобы продолжить выполнение с этой ошибки. См. поведение системы при обработке этого исключения в примерах ниже.Чтобы предотвратить прерывание выполнения после первой ошибки в Visual Studio, необходимо снять флажок "Только мой код" в меню Сервис, Параметры, Отладка, Общие.

Этот пример демонстрирует использование и может выполняться медленнее, чем аналогичный последовательный запрос LINQ to Objects.Дополнительные сведения об увеличении скорости см. в разделе Общее представление об ускорении выполнения в PLINQ.

Пример

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

Платформа PLINQ не помещает одно исключение OperationCanceledException в исключение System.AggregateException. Исключение OperationCanceledException должно обрабатываться в отдельном блоке catch. Если один или несколько пользовательских делегатов создают исключение OperationCanceledException(externalCT) (с помощью внешнего токена System.Threading.CancellationToken) и не создают другого исключения, а запрос был определен как AsParallel().WithCancellation(externalCT), PLINQ сгенерирует одно исключение OperationCanceledException (externalCT) вместо System.AggregateException. Однако если один пользовательский делегат создает исключение OperationCanceledException, а другой делегат создает другой тип исключения, оба исключения помещаются в AggregateException.

Ниже приводятся общие рекомендации по отмене.

  1. Если выполняется отмена с помощью пользовательского делегата, необходимо сообщить PLINQ о внешнем токене CancellationToken и создать исключение OperationCanceledException (externalCT).

  2. Если произошла отмена и другие исключения не созданы, необходимо обработать OperationCanceledException вместо AggregateException.

В следующем примере показана обработка отмены при наличии в пользовательском коде функции, требующей больших затрат компьютерных ресурсов.

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

При обработке отмены в пользовательском коде нет необходимости использовать метод WithCancellation<TSource> в определении запроса. Однако рекомендуется это делать, поскольку метод WithCancellation<TSource> не оказывает влияния на производительность запросов и позволяет обрабатывать отмену с помощью операторов запроса и пользовательского кода.

Чтобы обеспечить отклик системы, рекомендуется проверять отмену приблизительно каждую миллисекунду. Однако допускается любой период до 10 миллисекунд. Эта частота не должна оказывать отрицательное влияние на производительность кода.

При удалении перечислителя, например когда код прерывается вне цикла foreach (в Visual Basic — For Each), выполняющего итерацию результатов запроса, запрос отменяется, но исключение не создается.

См. также

Ссылки

ParallelEnumerable

Основные понятия

Parallel LINQ (PLINQ)

Отмена

Журнал изменений

Дата

Журнал

Причина

Май 2010

Добавлено примечание о сравнении использования и ускорения.

Обратная связь от клиента.