Как следует из приведенного выше результата, выполнение метода MyTask()
отменяется в методе Main()
лишь две секунды спустя. Следовательно, в методе MyTask()
выполняются четыре шага цикла. Когда же перехватывается исключение AggregateException
, проверяется состояние задачи. Если задача tsk
отменена, что и должно произойти в данном примере, то об этом выводится соответствующее сообщение. Следует, однако, иметь в виду, что когда сообщение AggregateException
генерируется в ответ на отмену задачи, то это еще не свидетельствует об ошибке, а просто означает, что задача была отменена.
Выше были изложены лишь самые основные принципы, положенные в основу отмены задачи и генерирования исключения AggregateException
. Тем не менее эта тема намного обширнее и требует от вас самостоятельного и углубленного изучения, если вы действительно хотите создавать высокопроизводительные, масштабируемые приложения.
Другие средства организации задач
В предыдущих разделах был описан ряд понятий и основных способов организации и исполнения задач. Но имеются и другие полезные средства. В частности, задачи можно делать вложенными, когда одни задачи способны создавать другие, или же порожденными, когда вложенные задачи оказываются тесно связанными с создающей их задачей.
В предыдущем разделе было дано краткое описание исключения AggregateException
, но у него имеются также другие особенности, которые могут оказаться весьма полезными. К их числу относится метод Flatten()
, применяемый для преобразования любых внутренних исключений типа AggregateException
в единственное исключение AggregateException
. Другой метод, Handle()
, служит для обработки исключения, составляющего совокупное исключение AggregateException
.
При создании задачи имеется возможность указать различные дополнительные параметры, оказывающие влияние на особенности ее исполнения. Для этой цели указывается экземпляр объекта типа TaskCreationOptions
в конструкторе класса Task
или же в фабричном методе StartNew()
. Кроме того, в классе TaskFactory
доступно целое семейство методов FromAsync()
, поддерживающих модель асинхронного программирования (АРМ — Asynchronous Programming Model).
Как упоминалось ранее в этой главе, задачи планируются на исполнение экземпляром объекта класса TaskScheduler
. Как правило, для этой цели предоставляется планировщик, используемый по умолчанию в среде .NET Framework. Но этот планировщик может быть настроен под конкретные потребности разработчика. Кроме того, допускается применение специализированных планировщиков задач.
Класс Parallel
В примерах, приведенных до сих пор в этой главе, демонстрировались ситуации, в которых библиотека TPL использовалась таким же образом, как и класс Thread
. Но это было лишь самое элементарное ее применение, поскольку в TPL имеются и другие средства. К их числу относится класс Parallel
, который упрощает параллельное исполнение кода и предоставляет методы, рационализирующие оба вида параллелизма: данных и задач.
Класс Parallel
является статическим, и в нем определены методы For(), For Each()
и Invoke()
. У каждого из этих методов имеются различные формы. В частности, метод For()
выполняет распараллеливаемый цикл for
, а метод ForEach()
— распараллеливаемый цикл foreach
, и оба метода поддерживают параллелизм данных. А метод Invoke()
поддерживает параллельное выполнение двух методов или больше. Как станет ясно дальше, эти методы дают преимущество реализации на практике распространенных методик параллельного программирования, не прибегая к управлению задачами или потоками явным образом. В последующих разделах каждый из этих методов будет рассмотрен более подробно.
Метод Invoke()
, определенный в классе Parallel
, позволяет выполнять один или несколько методов, указываемых в виде его аргументов. Он также масштабирует исполнение кода, используя доступные процессоры, если имеется такая возможность. Ниже приведена простейшая форма его объявления.
public static void Invoke(params Action[] actions)
Выполняемые методы должны быть совместимы с описанным ранее делегатом Action
. Напомним, что делегат Action
объявляется следующим образом.
public delegate void Action()
Следовательно, каждый метод, передаваемый методу Invoke()
в качестве аргумента, не должен ни принимать параметров, ни возвращать значение. Благодаря тому что параметр params
, выполняемые методы могут быть указаны в виде переменного списка аргументов. Для этой цели можно также воспользоваться массивом объектов типа Action
, но зачастую оказывается проще указать список аргументов.