- •1. Работа с числами
- •2. Представление даты и времени
- •3. Работа со строками и текстом
- •4. Преобразование информации
- •5. Сравнение для выяснения равенства
- •6. Сравнение для выяснения порядка
- •7. Жизненный цикл объектов
- •7.1. Алгоритм сборки мусора
- •7.2. Финализаторы и интерфейс IDisposable
- •7.3. Слабые ссылки
- •8. Перечислители и итераторы
- •9. Стандартные интерфейсы коллекций
- •10. Массивы и класс System.Array
- •11. Типы для работы с коллекциями-списками
- •12. Типы для работы с коллекциями-множествами
- •13. Типы для работы с коллекциями-словарями
- •14. Типы для создания пользовательских коллекций
- •15. Технология LINQ to Objects
- •16. Работа с объектами файловой системы
- •17. Ввод и вывод информации
- •17.1. Потоки данных и декораторы потоков
- •17.2. Адаптеры потоков
- •18. Основы XML и JSON
- •19. Технология LINQ to XML
- •20. Дополнительные возможности обработки XML
- •21. Сериализация времени выполнения
- •22. Сериализация контрактов данных
- •23. Состав и взаимодействие сборок
- •24. Метаданные и получение информации о типах
- •25. Позднее связывание и кодогенерация
- •26. Атрибуты
- •27. Динамическое связывание
- •28. Файлы конфигурации
- •29. Диагностика и мониторинг
- •30. Процессы и домены
- •31. Основы многопоточного программирования
- •32. Синхронизация потоков
- •32.1. Критические секции
- •32.2. Синхронизация на основе подачи сигналов
- •32.3. Неблокирующие средства синхронизации
- •32.4. Разделение данных между потоками
- •33. Выполнение асинхронных операций при помощи задач
- •33.1. Базовые сведения о задачах
- •33.2. Обработка исключений и отмена выполнения задач
- •33.3. Продолжения
- •33.4. Асинхронные функции в C#
- •34. Платформа параллельных вычислений
- •34.1. Параллелизм при императивной обработке данных
- •34.2. Параллелизм при декларативной обработке данных
- •34.3. Коллекции, поддерживающие параллелизм
- •35. Асинхронный вызов методов
- •Литература
После того как задача task1 (предшественник) завершается, отказывает или отменяется, задача task2 (продолжение) запускается. Аргумент t, переданный лямбда-выражению продолжения – это ссылка на предшествующую задачу1.
Выполнение продолжения можно запланировать на основе завершения множества предшествующих задач при помощи статических методов
Task.WhenAll() и Task.WhenAny().
var task1 = Task.Run(() => Console.Write("X")); var task2 = Task.Run(() => Console.Write("Y")); var continuation = Task.WhenAll(task1, task2)
.ContinueWith(t => Console.Write("Done"));
Второй способ организации продолжения заключается в использовании объекта ожидания. Объект ожидания – это любой объект, имеющий методы
OnCompleted() и GetResult() и булево свойство IsCompleted. Вызов метода
GetAwaiter() на задаче возвращает объект ожидания. Метод OnCompleted() принимает в качестве аргумента делегат, содержащий код продолжения. Метод GetResult() возвращает результат работы предшественника (или void).
//задача подсчёта простых чисел (используется LINQ)
Task<int> prime = Task.Run(() => Enumerable.Range(2, 3000000 - 2).Count(n =>
Enumerable.Range(2, (int) Math.Sqrt(n) - 1).All(i => n%i > 0)));
//получаем объект продолжения
var awaiter = prime.GetAwaiter();
// указываем, что делать после окончания предшественника awaiter.OnCompleted(() =>
{
// получаем результат вычислений предшественника int result = awaiter.GetResult(); Console.WriteLine(result);
});
33.4. Асинхронные функции в C#
Для поддержки асинхронного программирования в версии C# 5.0 появилась операция await. Синтаксис этой операции следующий:
var результат = await выражение; оператор(ы);
Компилятор разворачивает приведённую выше конструкцию в такой функциональный эквивалент:
1 По умолчанию предшественник и продолжение могут выполняться в разных потоках. Чтобы они выполнялись в одном потоке, передайте методу ContinueWith() дополнительный аргу-
мент со значением TaskContinuationOptions.ExecuteSynchronously.
144
var awaiter = выражение.GetAwaiter(); awaiter.OnCompleted(()=>
{
var результат = awaiter.GetResult();
оператор(ы);
}
Операция await применима и к выражению, не возвращающему значения:
await выражение; оператор(ы);
Выражение, на котором применяется await, обычно является задачей. Тем не менее, компилятор удовлетворит любой объект ожидания (определение данного понятия приведено выше). Операция await может применяться только внутри метода (или лямбда-выражения) со специальным модификатором async. Метод должен возвращать void либо тип Task или Task<TResult>.
Методы с модификатором async называются асинхронными функциями. Встретив выражение await, процесс выполнения (обычно) производит возврат в вызывающий код – почти как оператор yield return в итераторе. Но перед возвратом исполняющая среда присоединяет к ожидающей задаче признак продолжения, гарантирующий, что когда задача завершается, управление перейдёт обратно в метод и продолжится с места, в котором оно его оставило.
Рассмотрим несколько примеров использования операции await. Пусть имеется обычный метод, выполняющий чтение содержимого сайта. Для этого ис-
пользуется класс System.Net.WebClient и его метод DownloadString().
private void ReadFromWeb()
{
var web = new WebClient();
var text = web.DownloadString("http://msdn.com"); Console.WriteLine(text.Length);
}
Вызов метода ReadFromWeb() задержит основной поток выполнения на несколько миллисекунд (или даже секунд – зависит от скорости сети). К счастью,
создатели класса WebClient включили в него метод DownloadStringTaskAsync().
Этот метод создаёт задачу Task<string> для чтения сайта и возвращает управление. Данный факт позволяет нам превратить метод ReadFromWeb() в асинхронную функцию, которая не блокирует при вызове основной поток.
private async void ReadFromWeb()
{
var web = new WebClient();
var text = await web.DownloadStringTaskAsync("http://msdn.com"); Console.WriteLine(text.Length);
}
145