Класс Parallel

83

Одним из главных классов в TPL является System.Threading.Tasks.Parallel. Этот класс поддерживает набор методов, которые позволяют выполнять итерации по коллекции данных (точнее, по объектам, реализующим IEnumerable<T>) в параллельном режиме. Заглянув в документацию .NET Framework 4.0 SDK, вы увидите, что этот класс поддерживает два статических метода — Parallel.For() и Parallel.ForEach(), для каждого из которых определены многочисленные перегруженные версии.

Эти методы позволяют создавать тело операторов кода, которое может выполнятся в параллельном режиме. Концептуально эти операторы представляют собой логику того же рода, которая была бы написана в нормальной циклической конструкции (с использованием ключевых слов C# for и foreach). Однако их преимущество состоит в том, что класс Parallel самостоятельно берет потоки из пула потоков (и управляет конкуренцией).

Оба эти метода требуют указания совместимого с IEnumerable или IEnumerable<T> контейнера, хранящего данные, которые нужно обработать в параллельном режиме. Контейнер может быть простым массивом, необобщенной коллекцией (вроде ArrayList), обобщенной коллекцией (наподобие List<T>) или результатом запроса LINQ.

Вдобавок нужно будет использовать делегаты System.Func<T> и System.Action<T> для указания целевого метода, который будет вызываться для обработки данных. Вспомните, что Func<T> представляет метод, который возвращает значение и принимает различное количество аргументов. Делегат Action<T> очень похож на Func<T> в том, что позволяет указывать метод, принимающий несколько параметров. Однако Action<T> указывает метод, который может возвращать только void.

Распараллеливание задач методом Invoke()

Метод Invoke(), определенный в классе Parallel, позволяет выполнять один или несколько методов, указываемых в виде его аргументов. Он также масштабирует исполнение кода, используя доступные процессоры, если имеется такая возможность.

Ниже приведена простейшая форма его объявления:

public static void Invoke(params Action[] actions)

Выполняемые методы должны быть совместимы с описанным ранее делегатом Action. Следовательно, каждый метод, передаваемый методу Invoke() в качестве аргумента, не должен ни принимать параметров, ни возвращать значение.

Благодаря тому что параметр actions данного метода относится к типу params, выполняемые методы могут быть указаны в виде переменного списка аргументов. Для этой цели можно также воспользоваться массивом объектов типа Action, но зачастую оказывается проще указать список аргументов.

Метод Invoke() сначала инициирует выполнение, а затем ожидает завершения всех передаваемых ему методов. Это, в частности, избавляет от необходимости (да и не позволяет) вызывать метод Wait(). Все функции параллельного выполнения метод Invoke() берет на себя. И хотя это не гарантирует, что методы будут действительно выполняться параллельно, тем не менее, именно такое их выполнение предполагается, если система поддерживает несколько процессоров. Кроме того, отсутствует возможность указать порядок выполнения методов от первого и до последнего, и этот порядок не может быть таким же, как и в списке аргументов.

В приведенном ниже примере программы демонстрируется применение метода Invoke() на практике. В этой программе два метода MyMeth() и MyMeth2() выполняются параллельно посредством вызова метода Invoke(). Обратите внимание на простоту организации данного процесса:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication11_Parallel
{
    class Program
    {
        // Методы, исполняемые как задача
        static void MyMeth()
        {
            Console.WriteLine("MyMeth запущен");
            for (int count = 0; count < 5; count++)
            {
                Thread.Sleep(500);
                Console.WriteLine("--> MyMeth Count="+count);
            }
            Console.WriteLine("MyMeth завершен");
        }

        static void MyMeth2()
        {
            Console.WriteLine("MyMeth2 запущен");
            for (int count = 0; count < 5; count++)
            {
                Thread.Sleep(500);
                Console.WriteLine("--> MyMeth2 Count=" + count);
            }
            Console.WriteLine("MyMeth2 завершен");
        }

        static void Main()
        {
            Console.WriteLine("Основной поток запущен");

            // Выполнить параллельно оба именованных метода
            Parallel.Invoke(MyMeth, MyMeth2);

            Console.WriteLine("Основной поток завершен");
        }
    }
}
Пример распараллеливания задач методом Invoke()

В данном примере особое внимание обращает на себя следующее обстоятельство: выполнение метода Main() приостанавливается до тех пор, пока не произойдет возврат из метода Invoke(). Следовательно, метод Main(), в отличие от методов MyMeth() и MyMeth2(), не выполняется параллельно. Поэтому применять метод Invoke() показанным здесь способом нельзя в том случае, если требуется, чтобы исполнение вызывающего потока продолжалось.

Пройди тесты
Лучший чат для C# программистов