Выбрать главу
ь объект первой задачи. Task tsk = new Task(MyTask); // А теперь создать продолжение задачи. Task taskCont = tsk.ContinueWith(ContTask); // Начать последовательность задач. tsk.Start(); // Ожидать завершения продолжения. taskCont.Wait(); tsk.Dispose(); taskCont.Dispose(); Console.WriteLine("Основной поток завершен."); } } Ниже приведен результата выполнения данной программы. Основной поток запущен. MyTask() запущен В методе MyTask() подсчет равен 0 В методе MyTask() подсчет равен 1 В методе MyTask() подсчет равен 2 В методе MyTask() подсчет равен 3 В методе MyTask() подсчет равен 4 MyTask() завершен Продолжение запущено В продолжении подсчет равен 0 В продолжении подсчет равен 1 В продолжении подсчет равен 2 В продолжении подсчет равен 3 В продолжении подсчет равен 4 Продолжение завершено Основной поток завершен. Как следует из приведенного выше результата, вторая задача не начинается до тех пор, пока не завершится первая. Обратите также внимание на то, что в методе Main() пришлось ожидать окончания только продолжения задачи. Дело в том, что метод MyTask() как задача завершается еще до начала метода ContTask как продолжения задачи. Следовательно, ожидать завершения метода MyTask() нет никакой надобно сти, хотя если и организовать такое ожидание, то в этом будет ничего плохого. Любопытно, что в качестве продолжения задачи нередко применяется лямбда- выражение. Для примера ниже приведен еще один способ организации продолжения задачи из предыдущего примера программы. // В данном случае в качестве продолжения задачи применяется лямбда-выражение. Task taskCont = tsk.ContinueWith((first) => { Console.WriteLine("Продолжение запущено"); for(int count = 0; count < 5; count++) { Thread.Sleep(500); Console.WriteLine("В продолжении подсчет равен " + count ); } Console.WriteLine("Продолжение завершено"); } }; В этом фрагменте кода параметр first принимает предыдущую задачу (в данном случае — tsk). Помимо метода ContinueWith(), в классе Task предоставляются и другие методы, поддерживающие продолжение задачи, обеспечиваемое классом TaskFactory. К их чис лу относятся различные формы методов ContinueWhenAny() и ContinueWhenAll(), которые продолжают задачу, если завершится любая или все указанные задачи соот ветственно. Возврат значения из задачи Задача может возвращать значение. Это очень удобно по двум причинам. Во-первых, это означает, что с помощью задачи можно вычислить некоторый резуль тат. Подобным образом поддерживаются параллельные вычисления. И во-вторых, вы зывающий процесс окажется блокированным до тех пор, пока не будет получен ре зультат. Это означает, что для организации ожидания результата не требуется никакой особой синхронизации. Для того чтобы возвратить результат из задачи, достаточно создать эту задачу, ис пользуя обобщенную форму Task класса Task. Ниже приведены два кон структора этой формы класса Task: public Task(Func функция) public Task(Func функция, Object состояние) где функция обозначает выполняемый делегат. Обратите внимание на то, что он дол жен быть типа Func, а не Action. Тип Func используется именно в тех случаях, когда задача возвращает результат. В первом конструкторе создается задача без аргументов, а во втором конструкторе — задача, принимающая аргумент типа Object, передавае мый как состояние. Имеются также другие конструкторы данного класса. Как и следовало ожидать, имеются также другие варианты метода StartNew(), доступные в обобщенной форме класса TaskFactory и поддерживающие возврат результата из задачи. Ниже приведены те варианты данного метода, которые применяются параллельно с только что рассмотренными конструкторами класса Task. public Task StartNew(Func функция) public Task StartNew(Func функция, Object состояние) В любом случае значение, возвращаемое задачей, подучается из свойства Result в классе Task, которое определяется следующим образом. public TResult Result { get; internal set; } Аксессор set является внутренним для данного свойства, и поэтому оно оказывает ся доступным во внешнем коде, по существу, только для чтения. Следовательно, задача получения результата блокирует вызывающий код до тех пор, пока результат не будет вычислен. В приведенном ниже примере программы демонстрируется возврат задачей значе ний. В этой программе создаются два метода. Первый из них, MyTask(), не принимает параметров, а просто возвращает логическое значение true типа bool. Второй метод, SumIt(), принимает единственный параметр, который приводится к типу int, и воз вращает сумму из значения, передаваемого в качестве этого параметра. // Возвратить значение из задачи. using System; using System.Threading; using System.Threading.Tasks; class DemoTask { // Простейший метод, возвращающий результат и не принимающий аргументов. static bool MyTask() { return true; } // Этот метод возвращает сумму из положительного целого значения, // которое ему передается в качестве единственного параметра static int Sumlt(object v) { int x = (int) v; int sum = 0; for(; x > 0; x--) sum += x; return sum; } static void Main() { Console.WriteLine("Основной поток запущен."); // Сконструировать объект первой задачи. Task tsk = Task.Factory.StartNew(MyTask); Console.WriteLine("Результат после выполнения задачи MyTask: " + tsk.Result); // Сконструировать объект второй задачи. Task tsk2 = Task.Factory.StartNew(Sumlt, 3); Console.WriteLine("Результат после выполнения задачи Sumlt: " + tsk2.Result); tsk.Dispose(); tsk2.Dispose(); Console.WriteLine("Основной поток завершен."); } } Выполнение этой программы приводит к следующему результату. Основной поток запущен. Результат после выполнения задачи MyTask: True Результат после выполнения Sumlt: 6 Основной поток завершен. Помимо упомянутых выше форм класса Task и метода StartNew, имеются также другие формы. Они позволяют указывать другие дополнительные параметры. Отмена задачи и обработка исключения AggregateException В версии 4.0 среды .NET Framework внедрена новая подсистема, обеспечивающая структурированный, хотя и очень удобный способ отмены задачи. Эта новая подсисте ма основывается на понятии признака отмены. Признаки отмены поддерживаются в классе Task, среди прочего, с помощью фабричного метода StartNew(). ПРИМЕЧАНИЕ Новую подсистему отмены можно применять и для отмены потоков, рассматривавшихся в предыдущей главе, но она полностью интегрирована в TPL и PLINQ. Именно поэтому эта подсистема рассматривается в этой главе. Отмена задачи, как правило, выполняется следующим образом. Сначала полу чается признак отмены из источника признаков отмены. Затем этот признак пере дается задаче, после чего она должна контролировать его на предмет получения за проса на отмену. (Этот запрос может поступить только из источника признаков отмены.) Если получен запрос на отмену, задача должна завершиться. В одних слу чаях этого оказывается достаточно для простого прекращения задачи без каких- либо дополнительных действий, а в других — из задачи должен быть вызван метод ThrowIfCancellationRequested() для признака отмены. Благодаря этому в отме няющем коде становится известно, что задача отменена. А теперь рассмотрим процесс отмены задачи более подробно. Признак отмены является экземпляром объекта типа CancellationToken, т.е. структуры, определенной в пространстве имен System.Threading. В струк туре CancellationToken определено несколько свойств и методов, но мы вос пользуемся двумя из них. Во-первых, это доступное только для чтения свойство IsCancellationRequested, которое объявляется следующим образом. public bool IsCancellationRequested { get; } Оно возвращает логическое значение true, если отмена задачи была запрошена для вызывающего признака, а иначе — логическое значение false. И во-вторых, это метод ThrowIfCancellationRequested(), который объявляется следующим образом. public void ThrowIfCancellationRequested() Если признак отмены, для которого вызывается этот метод, получил запрос на от мену, то в данном методе генерируется исключение OperationCanceledException. В противном случае никаких действий не выполняется. В отменяющем коде можно организовать отслеживание упомянутого исключения с целью убедиться в том, что отмена задачи действительно произошла. Как правило, с этой целью сначала пере хватывается исключение AggregateException, а затем его внутреннее исключение анализируется с помощью свойства InnerException или InnerExceptions. (Свой ство InnerExceptions представляет собой коллекцию исключений. Подробнее о кол лекциях речь пойдет в главе 25.) Признак отмены получается из источника признаков отмены, который пред ставляет собой объект класса CancellationTokenSource, определенного в про странстве имен System. Threading. Для того чтобы получить данный признак, нуж но создать сначала экземпляр объекта типа CancellationTokenSource. (С этой целью можно воспользоваться вызываемым по умолчанию конструктором класса CancellationTokenSource.) Признак отмены, связанный с данным источником, ока зывается доступным через используемое только для чтения свойство Token, которое объявляется следующим образом. public CancellationToken Token { get; } Это и есть тот признак, который должен быть передан отменяемой задаче. Для отмены в задаче должна быть получена копия признака отмены и организо ван контроль этого признака с целью отслеживать саму отмену. Такое отслеживание можно организовать тремя способами: опросом, методом обратного вызова и с по мощью дескриптора ожидания. Проще всего организовать опрос, и поэтому здесь бу дет рассмотрен именно этот способ. С целью опроса в задаче проверяется упомянутое выше свойство IsCancellationRequested признака отмены. Если это свойство со держит логическое значение true, значит, отмена была запрошена, и задача долж на быть завершена. Опрос может оказаться весьма эффективным, если организовать его правильно. Так, если задача содержит вложенные циклы, то проверка свойства IsCancellationRequested во внешнем цикле зачастую дает лучший результат, чем его проверка на каждом шаге внутреннего цикла. Для создания задачи, из которой вызывается метод ThrowIfCancellationRequested(), когда она отменяется, обычно требуется передать признак отмены как самой задаче, так и конструктору класса Task, будь то непосредственно или же косвенно через метод StartNew(). Передача признака отмены самой задаче позволяет изменить состояние от меняемой задачи в запросе на отмену из внешнего кода. Далее будет использована сле дующая форма метода StartNew(). public Task StartNew(Action