Урок 7. Использование Parallel.Invoke C#


Начнем рассмотрение параметров, которые библиотека параллельных задач (TPL) предоставляет для декомпозиции задач. Седьмой урок учебника по параллельному программированию в .NET описывает метод Parallel.Invoke, который может вызывать несколько делегатов параллельно.

Параллелизм задач

На предыдущих уроках учебника мы рассмотрели некоторые концепции параллельного программирования с помощью .NET framework, а затем несколько статей, описывающих декомпозицию данных и параллелизм с использованием Parallel.For и Parallel.ForEach для циклов. Циклы выполняют один и тот же алгоритм много раз в наборе итераций. Итерации могут быть разбиты на блоки и разделены между несколькими процессорами или ядрами для параллельного выполнения.

Некоторые алгоритмы не поддаются декомпозиции данных, потому что они не повторяют одно и то же действие. Однако они могут быть кандидатами на декомпозицию задач. Здесь алгоритм разбивается на разделы, которые могут выполняться независимо друг от друга. Каждая секция рассматривается как отдельная задача, которая может быть выполнена на собственном процессорном ядре, причем несколько задач выполняются одновременно. Этот тип декомпозиции обычно сложнее реализовать и иногда требует, чтобы алгоритм был существенно изменен или полностью заменен, чтобы минимизировать элементы, которые должны выполняться последовательно, и ограничить общие изменяемые значения.

Parallel.Invoke

Мы начнем наше исследование параллелизма задач в .NET с рассмотрения метода Parallel.Invoke. Этот метод обеспечивает простой способ, с помощью которого ряд задач может быть создан и выполнен параллельно. Как и другие методы в библиотеке параллельных задач, он предоставляет потенциальный параллелизм. Это значит, что если создание нескольких потоков выполнения не принесет никакой пользы, задачи будут выполняться последовательно.

При использовании Parallel.Invoke, задачи, которые должны быть выполнены, предоставляются в качестве делегатов. Метод использует массив параметров для делегатов, чтобы разрешить создание любого количества задач. Задачи обычно определяются с помощью лямбда-выражений, но вместо них могут использоваться анонимные методы и простые делегаты. После вызова все задачи выполняются до того, как обработка продолжается командой, следующей за Parallel.Invoke оператор. Порядок выполнения отдельных делегатов не гарантируется, поэтому не следует полагаться на то, что результаты одной операции будут доступны для той, которая появится позже в массиве параметров.

Чтобы продемонстрировать метод Parallel.Invoke мы создадим консольное приложение. Параллельный класс находится в System.Threading.Tasks пространства имен, а также мы будем использовать класс Thread из System.Threading пространство имен в программе, поэтому добавьте следующие директивы using:

using System.Threading.Tasks;
using System.Threading; 
В следующем примере используется Parallel.Invoke для запуска трех отдельных задач. Каждый выводит сообщение о том, что задача запущена. Затем следует пауза от одной до пяти секунд, прежде чем будет выведено второе сообщение, указывающее на завершение задачи.

Parallel.Invoke(
    () => {
        Console.WriteLine("Задание 1 начато");
        Thread.Sleep(5000);
        Console.WriteLine("Задание 1 выполнено");
    },
    () => {
        Console.WriteLine("Задание 2 начато");
        Thread.Sleep(3000);
        Console.WriteLine("Задание 2 выполнено");
    },
    () => {
        Console.WriteLine("Задание 3 начато");
        Thread.Sleep(1000);
        Console.WriteLine("Задание 3 выполнено");
    });
  
/* Вывод
  
Задание 2 начато
Задание 1 начато
Задание 3 начато
Задание 3 выполнено
Задание 2 выполнено
Задание 1 выполнено
 
*/
Результаты, приведенные в комментарии, являются результатами одного запуска программы на машине с двухъядерным процессором. Интересно отметить, что в этом случае вторая задача в массиве параметров начиналась раньше первой. Вы можете видеть, что все три задачи выполнялись параллельно, хотя было доступно только два ядра. Это происходит потому, что параллельная библиотека задач определила, что процессоры простаивают, когда задачи приостанавливаются методом Thread.Sleep. Это позволило эффективно приступить к выполнению последней задачи. Обратите внимание, что результаты варьируются от запуска к запуску, и порядок выполнения может меняться.

Перехват исключений из Parallel.Invoke

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

В следующем примере мы снова запускаем три процесса, которые могут выполняться параллельно. Первый выходит нормально. Вторая попытка деления на ноль, поэтому создается исключение. Третья задача создает исключение непосредственно из кода. Parallel.Invoke содержится в блоке try/catch, который обрабатывает только AggregateExceptions, выводя свойство Message любых возникающих исключений. Результаты показывают, что два исключения, которые мы ожидаем, находятся в свойстве InnerExceptions AggegateException.

try
{
    Parallel.Invoke(
    () => {
        Console.WriteLine("Task 1 started");
        Thread.Sleep(5000);
        Console.WriteLine("Task 1 complete");
    },
    () => {
        Console.WriteLine("Task 2 started");
        int i = 0;
        Console.WriteLine(1 / i);
    },
    () => {
        Console.WriteLine("Task 3 started");
        Thread.Sleep(1000);
        throw new InvalidOperationException("Test Exception");
    });
}
catch (AggregateException ex)
{
    Console.WriteLine("\nThere were exceptions:\n");
    foreach (var exception in ex.InnerExceptions)
    {
        Console.WriteLine(exception.Message);
    }
}
 
/* Вывод
 
Task 1 started
Task 2 started
Task 3 started
Task 1 complete
 
There were exceptions:
 
Test Exception
Attempted to divide by zero.
 
*/
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегистатьи IT, параллельное программирование, си шарп




Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.



Урок 11. Задачи продолжения C#
Java: наибольшее число, оканчивающееся на 0
Что такое блокчейн?