C# 4.0: полное руководство - Герберт Шилдт
Шрифт:
Интервал:
Закладка:
}
static void Main() {
Console.WriteLine("Основной поток запущен.");
// Создать объект источника признаков отмены.
CancellationTokenSource cancelTokSrc = new CancellationTokenSource();
// Запустить задачу, передав признак отмены ей самой и делегату.
Task tsk = Task.Factory.StartNew(MyTask, cancelTokSrc.Token,
cancelTokSrc.Token);
// Дать задаче возможность исполняться вплоть до ее отмены.
Thread.Sleep(2000);
try {
// Отменить задачу.
cancelTokSrc.Cancel();
// Приостановить выполнение метода Main() до тех пор,
// пока не завершится задача tsk.
tsk.Wait();
} catch (AggregateException exc) {
if(tsk.IsCanceled)
Console.WriteLine("nЗадача tsk отменена");
// Для просмотра исключения снять комментарии со следующей строки кода:
// Console.WriteLine(exc);
} finally {
tsk.Dispose();
cancelTokSrc.Dispose();
}
Console.WriteLine("Основной поток завершен.");
}
}
Ниже приведен результат выполнения этой программы. Обратите внимание на то что задача отменяется через 2 секунды.
Основной поток запущен.
MyTask() запущен
В методе MyTask() подсчет равен 0
В методе MyTask() подсчет равен 1
В методе MyTask() подсчет равен 2
В методе MyTask() подсчет равен 3
Получен запрос на отмену задачи.
Задача tsk отменена
Основной поток завершен.
Как следует из приведенного выше результата, выполнение метода 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()Метод Invoke(), определенный в классе Parallel, позволяет выполнять один или несколько методов, указываемых в виде его аргументов. Он также масштабирует исполнение кода, используя доступные процессоры, если имеется такая возможность. Ниже приведена простейшая форма его объявления.
public static void Invoke(params Action[] actions)
Выполняемые методы должны быть совместимы с описанным ранее делегатом Action. Напомним, что делегат Action объявляется следующим образом.
public delegate void Action()
Следовательно, каждый метод, передаваемый методу Invoke() в качестве аргумента, не должен ни принимать параметров, ни возвращать значение. Благодаря тому что параметр actions данного метода относится к типу params, выполняемые методы могут быть указаны в виде переменного списка аргументов. Для этой цели можно также воспользоваться массивом объектов типа Action, но зачастую оказывается проще указать список аргументов.
Метод Invoke() сначала инициирует выполнение, а затем ожидает завершения всех передаваемых ему методов. Это, в частности, избавляет от необходимости (да и не позволяет) вызывать метод Wait(). Все функции параллельного выполнения метод Wait() берет на себя. И хотя это не гарантирует, что методы будут действительно выполняться параллельно, тем не менее, именно такое их выполнение предполагается, если система поддерживает несколько процессоров. Кроме того, отсутствует возможность указать порядок выполнения методов от первого и до последнего, и этот порядок не может быть таким же, как и в списке аргументов.
В приведенном ниже примере программы демонстрируется применение метода Invoke() на практике. В этой программе два метода MyMeth() и MyMeth2() выполняются параллельно посредством вызова метода Invoke(). Обратите внимание на простоту организации данного процесса.
// Применить метод Parallel.Invoke() для параллельного выполнения двух методов.
using System;
using System.Threading;
using System.Threading.Tasks;
class DemoParallel {
// Метод, исполняемый как задача,
static void MyMeth() {
Console.WriteLine("MyMeth запущен");
for (int count = 0; count < 5; count++) {
Thread.Sleep(500);
Console.WriteLine("В методе MyMeth подсчет равен " + count );
}
Console.WriteLine("MyMeth завершен");
}
// Метод, исполняемый как задача,
static void MyMeth2() {
Console.WriteLine("MyMeth2 запущен");