Рейтинговые книги
Читем онлайн Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 189 190 191 192 193 194 195 196 197 ... 407
часто встречающихся слов.

      tenMostCommon = FindTenMostCommon(words);

    },

    () =>

    {

      // Найти самое длинное слово.

      longestWord = FindLongestWord(words);

    });

  // Когда все задачи завершены, построить строку,

  // показывающую все статистические данные.

  ...

}

Метод Parallel.Invoke() ожидает передачи в качестве параметра массива делегатов Action<>, который предоставляется косвенно с применением лямбда-выражения. В то время как вывод идентичен, преимущество заключается в том, что библиотека TPL теперь будет использовать все доступные процессоры машины для вызова каждого метода параллельно, если подобное возможно.

Запросы Parallel LINQ (PLINQ)

В завершение знакомства с библиотекой TPL следует отметить, что существует еще один способ встраивания параллельных задач в приложения .NET Core. При желании можно применять набор расширяющих методов, которые позволяют конструировать запрос LINQ, распределяющий свою рабочую нагрузку по параллельным потокам (когда это возможно). Соответственно запросы LINQ, которые спроектированы для параллельного выполнения, называются запросами Parallel LINQ (PLINQ).

Подобно параллельному коду, написанному с использованием класса Parallel, в PLINQ имеется опция игнорирования запроса на обработку коллекции параллельным образом, если понадобится. Инфраструктура PLINQ оптимизирована во многих отношениях, включая определение того, не будет ли запрос на самом деле более эффективно выполняться в синхронной манере.

Во время выполнения PLINQ анализирует общую структуру запроса, и если есть вероятность, что запрос выиграет от распараллеливания, то он будет выполняться параллельно. Однако если распараллеливание запроса ухудшит производительность, то PLINQ просто запустит запрос последовательно. Когда возникает выбор между потенциально затратным (в плане ресурсов) параллельным алгоритмом и экономным последовательным, предпочтение по умолчанию отдается последовательному алгоритму.

Необходимые расширяющие методы находятся в классе ParallelEnumerable из пространства имен System.Linq. В табл. 15.5 описаны некоторые полезные расширяющие методы PLINQ.

Чтобы взглянуть на PLINQ в действии, создайте проект консольного приложения по имени PLINQDataProcessingWithCancellation и импортируйте в него пространства имен System.Linq, System.Threading и System.Threading.Tasks (если это еще не сделано). После начала обработки запускается новая задача, выполняющая запрос LINQ, который просматривает крупный массив целых чисел в поиске элементов, удовлетворяющих условию, что остаток от их деления на 3 дает 0. Вот непараллельная версия такого запроса:

using System;

using System.Linq;

using System.Threading;

using System.Threading.Tasks;

Console.WriteLine("Start any key to start processing");

                // Нажмите любую клавишу, чтобы начать обработку

Console.ReadKey();

Console.WriteLine("Processing");

Task.Factory.StartNew(ProcessIntData);

Console.ReadLine();

void ProcessIntData()

{

  // Получить очень большой массив целых чисел.

  int[] source = Enumerable.Range(1, 10_000_000).ToArray();

  // Найти числа, для которых истинно условие num % 3 == О,

  // и возвратить их в убывающем порядке.

  int[] modThreeIsZero = (

    from num in source

    where num % 3 == 0

    orderby num descending

    select num).ToArray();

  // Вывести количество найденных чисел

  Console.WriteLine($"Found {modThreeIsZero.Count()} numbers

                      that match query!");

}

Создание запроса PLINQ

Чтобы проинформировать библиотеку TPL о выполнении запроса в параллельном режиме (если такое возможно), необходимо использовать расширяющий метод AsParallel():

int[] modThreeIsZero = (

  from num in source.AsParallel()

  where num % 3 == 0

  orderby num descending select num).ToArray();

Обратите внимание, что общий формат запроса LINQ идентичен тому, что вы видели в предыдущих главах. Тем не менее, за счет включения вызова AsParallel() библиотека TPL попытается распределить рабочую нагрузку по доступным процессорам. 

Отмена запроса PLINQ

 С помощью объекта CancellationTokenSource запрос PLINQ можно также информировать о прекращении обработки при определенных условиях (обычно из-за вмешательства пользователя). Объявите на уровне класса Program объект CancellationTokenSource по имени _cancelToken и модифицируйте операторы верхнего уровня для принятия ввода от пользователя. Ниже показаны соответствующие изменения в коде:

CancellationTokenSource _cancelToken =

  new CancellationTokenSource();

do

{

  Console.WriteLine("Start any key to start processing");

                  // Нажмите любую клавишу, чтобы начать обработку

  Console.ReadKey();

  Console.WriteLine("Processing");

  Task.Factory.StartNew(ProcessIntData);

  Console.Write("Enter Q to quit: ");

              // Введите Q для выхода:

  string answer = Console.ReadLine();

  // Желает ли пользователь выйти?

  if (answer.Equals("Q",

    StringComparison.OrdinalIgnoreCase))

  {

    _cancelToken.Cancel();

    break;

  }

}

while (true);

Console.ReadLine();

Теперь запрос PLINQ необходимо информировать о том, что он должен ожидать входящего запроса на отмену выполнения, добавив в цепочку вызов расширяющего метода WithCancellation() с передачей ему маркера отмены. Кроме того, этот запрос PLINQ понадобится поместить в подходящий блок try/catch и обработать возможные исключения. Финальная версия метода ProcessInData() выглядит следующим образом:

void ProcessIntData()

{

  // Получить очень большой массив целых чисел.

  int[] source = Enumerable.Range(1, 10_000_000).ToArray();

  // Найти числа, для которых истинно условие num % 3 == 0,

  // и возвратить их в убывающем порядке.

  int[] modThreeIsZero = null;

  try

  {

    modThreeIsZero =

      (from num in source.AsParallel().WithCancellation(_cancelToken.Token)

            where num % 3 == 0

            orderby num descending

            select num).ToArray();

    Console.WriteLine();

    // Вывести количество найденных чисел.

    Console.WriteLine($"Found {modThreeIsZero.Count()} numbers

                        that match query!");

  }

  catch (OperationCanceledException ex)

  {

    Console.WriteLine(ex.Message);

  }

}

Во время выполнения метода ProcessIntData() понадобится нажать <Q> и быстро произвести ввод, чтобы увидеть сообщение от маркера отмены.

Асинхронные вызовы с помощью async/await

В этой довольно длинной главе было представлено много материала в сжатом виде. Конечно, построение, отладка и понимание сложных многопоточных приложений требует прикладывания усилий в любой инфраструктуре. Хотя TPL, PLINQ и тип делегата могут до некоторой степени упростить решение (особенно по сравнению с другими платформами и языками), разработчики по-прежнему должны хорошо знать детали разнообразных расширенных приемов.

С выходом версии .NET 4.5 в языке

1 ... 189 190 191 192 193 194 195 196 197 ... 407
На этой странице вы можете бесплатно читать книгу Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен бесплатно.

Оставить комментарий