Позволял бы с выгодой использовать несколько процессов для повышения производительности программного обеспечения. Но дело в том, что в прошлом это было не так-то просто сделать ясным и допускающим масштабирование способом. Изменить это положение, собственно, и призваны TPL и PLINQ. Ведь они дают возможность легче (и безопаснее) использовать системные ресурсы.
Библиотека TPL определена в пространстве имен System. Threading. Tasks. Но для работы с ней обычно требуется также включать в программу класс System. Threading, поскольку он поддерживает синхронизацию и другие средства многопоточной обработки, в том числе и те, что входят в класс Interlocked.
В этой главе рассматривается и TPL, и PLINQ. Следует, однако, иметь в виду, что и та и другая тема довольно обширны. Поэтому в этой главе даются самые основы и рассматриваются некоторые простейшие способы применения TPL и PLINQ. Таким образом, материал этой главы послужит вам в качестве удобной отправной точки для дальнейшего изучения TPL и PLINQ. Если параллельное программирование входит в сферу ваших интересов, то именно эти средства.NET Framework вам придется изучить более основательно.
ПРИМЕЧАНИЕ
Несмотря на то что применение TPL и PLINQ рекомендуется теперь для разработки большинства многопоточных приложений, организация многопоточной обработки на основе класса Thread, представленного в главе 23, по-прежнему находит широкое распространение. Кроме того, многое из того, что пояснялось в главе 23, применимо и к TPL. Поэтому усвоение материала главы 23 все еще необходимо для полного овладения особенностями организации многопоточной обработки на С#.
Два подхода к параллельному программированию
Применяя TPL, параллелизм в программу можно ввести двумя основными способами. Первый из них называется параллелизмом данных. При таком подходе одна операция над совокупностью данных разбивается на два параллельно выполняемых потока или больше, в каждом из которых обрабатывается часть данных. Так, если изменяется каждый элемент массива, то, применяя параллелизм данных, можно организовать параллельную обработку разных областей массива в двух или больше потоках. Нетрудно догадаться, что такие параллельно выполняющиеся действия могут привести к значительному ускорению обработки данных по сравнению с последовательным подходом. Несмотря на то что параллелизм данных был всегда возможен и с помощью класса Thread, построение масштабируемых решений средствами этого класса требовало немало усилий и времени. Это положение изменилось с появлением библиотеки TPL, с помощью которой масштабируемый параллелизм данных без особого труда вводится в программу.
Второй способ ввода параллелизм называется параллелизмом задач. При таком подходе две операции или больше выполняются параллельно. Следовательно, параллелизм задач представляет собой разновидность параллелизма, который достигался в прошлом средствами класса Thread. А к преимуществам, которые сулит применение TPL, относится простота применения и возможность автоматически масштабировать исполнение кода на несколько процессоров.
Класс Task
В основу TPL положен класс Task. Элементарная единица исполнения инкапсулируется в TPL средствами класса Task, а не Thread. Класс Task отличается от класса Thread тем, что он является абстракцией, представляющей асинхронную операцию. А в классе Thread инкапсулируется поток исполнения. Разумеется, на системном уровне поток по-прежнему остается элементарной единицей исполнения, которую можно планировать средствами операционной системы. Но соответствие экземпляра объекта класса Task и потока исполнения не обязательно оказывается взаимно-однозначным. Кроме того, исполнением задач управляет планировщик задач, который работает с пулом потоков. Это, например, означает, что несколько задач могут разделять один и тот же поток. Класс Task (и вся остальная библиотека TPL) определены в пространстве имен System.Threading.Tasks.
Создание задачи
Создать новую задачу в виде объекта класса Task и начать ее исполнение можно самыми разными способами. Для начала создадим объект типа Task с помощью конструктора и запустим его, вызвав метод Start (). Для этой цели в классе Task определено несколько конструкторов. Ниже приведен тот конструктор, которым мы собираемся воспользоваться:
public Task(Action действие)
где действие обозначает точку входа в код, представляющий задачу, тогда как Action — делегат, определенный в пространстве имен System. Форма делегата Action, которой мы собираемся воспользоваться, выглядит следующим образом.