NET, Скасування завдання
У версії 4.0 середовища .NET Framework впроваджено нову підсистему, що забезпечує структурований, хоч і дуже зручний спосіб скасування завдання. Ця нова підсистема ґрунтується на понятті ознаки скасування. Ознаки скасування підтримуються в класі Task, серед іншого, за допомогою фабричного методу StartNew().
Скасування завдання, зазвичай, виконується так. Спочатку виходить ознака скасування із джерела ознак скасування. Потім ця ознака передається задачі, після чого вона повинна контролювати його на предмет отримання запиту на скасування. (Цей запит може надходити лише з джерела ознак скасування.) Якщо отриманий запит на скасування, завдання має завершитись.
В одних випадках цього виявляється достатньо для простого припинення завдання без будь-яких додаткових дій, а в інших — із завдання повинен бути викликаний метод ThrowIfCancellationRequested() для ознаки скасування. Завдяки цьому у коді, що скасовує, стає відомо, що завдання скасовано. А тепер розглянемо процес скасування завдання докладніше.
Ознака скасування є екземпляром об'єкта типу CancellationToken, тобто. Структура, визначена в просторі імен System.Threading. У структурі CancellationToken визначено кілька властивостей та методів, але ми скористаємось двома з них. По-перше, це доступна тільки для читання властивістьIsCancellationRequested. Воно повертає логічне значення true, якщо скасування завдання було запрошено для ознаки, що викликає, а інакше — логічне значення false. І по-друге, це метод ThrowIfCancellationRequested().
Якщо ознака скасування, якого викликається цей метод, отримав запит на скасування, то цьому методі генерується виняток OperationCanceledException. В іншому випадку ніяких дій невиконується. У коді, що скасовує, можна організувати відстеження згаданого виключення з метою переконатися в тому, що скасування завдання дійсно відбулося. Як правило, з цією метою спочатку перехоплюється виняток AggregateException, а потім його внутрішній виняток аналізується за допомогою властивості InnerException або InnerExceptions. (Властивість InnerExceptions є колекцією винятків.)
Ознака скасування виходить із джерела ознак скасування, який є об'єкт класу CancellationTokenSource, визначеного у просторі імен System.Threading. Щоб отримати цю ознаку, потрібно створити спочатку екземпляр об'єкта типу CancellationTokenSource. (З цією метою можна скористатися конструктором класу CancellationTokenSource, що викликається за замовчуванням.) Ознака скасування, пов'язана з даним джерелом, виявляється доступною через використовувану тільки для читання властивістьToken. Це і є та ознака, яка має бути передана скасовуваній задачі.
Для скасування у завданні має бути отримана копія ознаки скасування та організований контроль цієї ознаки з метою відстежувати саме скасування. Таке відстеження можна організувати трьома способами: опитуванням, методом зворотного виклику та за допомогою дескриптора очікування. Найпростіше організувати опитування, і тому буде розглянуто саме цей спосіб. З метою опитування в задачі перевіряється згадана вище властивість IsCancellationRequested ознаки скасування. Якщо ця властивість містить логічне значення true, значить скасування було запитано, і завдання має бути завершено.
Опитування може бути дуже ефективним, якщо організувати його правильно. Так, якщо завдання містить вкладені цикли, то перевірка властивості IsCancellationRequested у зовнішньому циклі найчастіше дає кращий результат, ніж йогоперевірка кожного кроку внутрішнього циклу.
Для створення завдання, з якого викликається метод ThrowIfCancellationRequested(), коли вона скасовується, зазвичай потрібно передати ознаку скасування як самій задачі, так і конструктору класу Task, чи то безпосередньо, чи опосередковано через метод StartNew(). Передача ознаки скасування самій задачі дозволяє змінити стан завдання, що скасовується в запиті на скасування із зовнішнього коду.
У цій формі ознака скасування передається через параметри, що позначаються як стан та ознака скасування. Це означає, що ознака скасування буде переданий як делегату, що реалізує завдання, так і екземпляру об'єкта типу Task.
Факт скасування завдання може бути перевірений різними способами. Тут застосовується наступний підхід: перевірка значення якості IsCanceled для екземпляра об'єкта типу Task. Якщо це логічне значення true, то завдання було скасовано.
У наведеній нижче програмі демонструється скасування завдання. У ній застосовується опитування контролю стану ознаки скасування. Зверніть увагу, що метод ThrowIfCancellationRequested() викликається після входу в метод MyTask(). Це дає можливість завершити завдання, якщо воно було скасовано ще до його запуску. Всередині циклу перевіряється властивість IsCancellationRequested. Якщо ця властивість містить логічне значення true, а вона встановлюється після виклику методу Cancel() для екземпляра джерела ознак скасування, то на екран виводиться повідомлення про скасування і далі викликається метод ThrowIfCancellationRequested() для скасування завдання:
Як випливає з наведеного вище прикладу, виконання методу MyTask() скасовується в методі Main() лише через дві секунди. Отже, у методі MyTask() виконуються чотири кроки циклу. Коли ж перехоплюється виняток AggregateException,перевіряється стан завдання. Якщо завдання tsk скасовано, що має відбутися у цьому прикладі, про це виводиться відповідне повідомлення. Слід, проте, пам'ятати, що коли повідомлення AggregateException генерується у відповідь скасування завдання, це ще свідчить про помилку, а це означає, що завдання було скасовано.
Вище були викладені лише основні принципи, покладені основою скасування завдання й генерування винятку AggregateException. Проте ця тема набагато ширша і вимагає від вас самостійного та поглибленого вивчення, якщо ви дійсно хочете створювати високопродуктивні, масштабовані програми.