Представьте - используете вы ваше любимое и вдруг, неожиданно оно подвисает. Вы не можете взаимодействовать с ним и UI никак не обновляется, пока вдруг все не возвращается в нормальное русло. Звучит знакомо? Я уверен, что это уже случалось с вами. Зачастую это происходит от выполнения продолжительных операций в UI-потоке, который никак не может заботиться об интерфейсе, пока все операции не будут завершены.
Для обеспечения работы UI потенциально длительные операции должны быть выполнены асинхронно, в отдельных потоках. Здесь ничего нового, но многие приложения до сих пор выполняют операции в UI-потоке. Почему? Одна из причин в том, что асинхронное программирование неудобно. Это гораздо более сложнее, тяжелее писать, тестировать, поддерживать, и если не написать должным образом это может привести к снижению производительности и блокировкам.
На протяжении многих лет версии .NET предлагали постепенно совершенствующиеся модели которые пытаются уменьшить сложность асинхронного программирования.
Но так было до .NET 4.5 / C# 5 - наконец мы можем писать асинхронный код так же просто, как и обыкновенный, а компилятор уже сам сделает всю тяжелую работу за нас (страшновато звучит...). По факту асинхронный код может быть почти идентичным его синхронному аналогу. Теперь без лишних слов давайте рассмотрим примеры async/await
Введение в acync/await
Представьте, что у нас есть приложение Windows Forms и когда пользователь нажимает на кнопку мы качаем картинку из веба, а после показываем ее в UI. Следующий код делает это синхронно:Теперь представьте, что интернет медленный и скачивание картинки занимает 30 секунд. На протяжении этого времени приложение не будет отвечать. Если пользователь нетерпелив, он может остановить работу приложения до окончания загрузки изображения. В общем это не очень хороший подход. Мы должны загружать изображение асинхронно. Давайте посмотрим как мы можем легко сделать это используя async/await:
Этот код почти идентичен! В представленном коде три отличия: метод обозначен ключевым словом async, вызову загрузки картинки предшествует await и картинка загружается методом DownloadDataTaskAsync, являющимся асинхронным аналогом метода DownloadData. Давайте рассмотрим каждое изменение более подробно.
Ключевое слово async говорит компилятору, что метод может содержать ключевое слово await. Это необходимо для обратной совместимости с кодом, написанным до введения await в C# 5, где await можно было использовать в качестве идентификатора. (т.е. если метод не обозначить как async, то await можно использовать в качестве имени переменной, иначе - нельзя). Это также позволяет компилятору быть уверенным в том, что мы используем await внутри метода, и выдавать исключение, если мы забыли сделать это.
Метод DownloadData класса Webclient загружает данные синхронно, до возвращения управления вызывающему. Но нам нужно, чтобы метод возвращал управление немедленно и выполнял загрузку асинхронно. Многие .NET FCL классы сегодня предлагают асинхронные версии своих методов, где это, конечно, возможно. Как правило асинхронные методы, к коим можно применить await, именуются как xxxAsync. Однако в случае класса WebClient там уже был метод DownloadDataAsync до .NET 4.5, так что был добавлен метод DownloadDataTaskAsync. Если вы сомневаетесь, какой метод использовать с await, то задпомните одно: await может быть использован только с методами возлвращающими Task или Task<T>
Ключевое слово await пожалуй самое интересное изменение в коде. Как только мы начинаем загрузку асинхронно, мы хотим освободить UI-поток до тех пор, пока скачивание не закончится. await делает именно это. Как только наш код встречает await происходит возврат управления. После завершения ожидаемой операции метод восстанавливается. Точнее продолжает выполнение с того места, на котором остановился, когда столкнулся с await.
Использование await
В примере выше мы использовали await однажды, но мы можем использовать его несколько раз в одном методе. Например, мы можем скачивать несколько изображений. Это значит, что у метода будет несколько возвратов и восстановлений.Другая интересная сторона await в том, что мы можем использовать его почти везде в коде, не только при простом вызове метода. Рассмотрим пересмотренный обработчик button_Click:
Правила и ограничения
В то время как использование async/await очень гибко, оно омеет некоторые ограничения. Наиболее заметные из них:- await не может быть использован внутри блоков catch/finally
- Конструкторы и аксессоры свойств не могут быть помечены как async и использовать await
- async метод не может иметь ref/out параметры
- await не может быть использован внутри блока lock
Управление исключениями
Асинхронное программирование в C# 5 было устроено как можно безболезненее и управление исключениями не исключение (каламбур). По факту мы пишем код управления исключениями точно так же как и в синхронном варианте кода:Хотя работа с рагрузкой изображения происходит в другом потоке и, следовательно, исключение может быть брошено из другого потока, оно отлавливается и перепробрасывается, когда управление возвращается к ожидающему методу. Это позволяет нашему коду обрабатывать исключения как если бы мы делали синхронную обработку.
Один интересный вопрос, что происходит с finnaly блоками? Рассмотрим следующий код:
finnaly блоки гарантированно выполняются, когда управление покидает соответствующие try/catch блоки. в результате, это также гарантирует выполнение если метод возвращает результат из try/catch блока. Как мы уже знаем, await делает возврат. Так что же произойдет в коде выше? Будет ли код в finally выполняться когда возвращается выолнение в await или будет выполнен только после восстановление метода и когда код логически покинет код try?
Всякий раз, когда вы задаете себе подобные вопросы, полезно иметь ввиду, что поддержка асинхронности в C# 5 (?) была разработана чтобы чувствовать себя естественно, как в синхронном коде (по возможности). В асинхронном коде происходит то, что в общем происходило бы и в синхронном коде. Если бы код выше был синхронным, вы бы ожидали выполнение finnaly-блока после завершения блока try (при условии отсутствия исключений). Такое же поведение применимо и к асинхронному коду. Даже, если технически метод возвращает результат await, логически при await ничего еще не вернулось. Следовательно finally-блок в коде выше выполняется когда код try-блока завершится (или пробросится исключение), собтвенно как и в синхронном варианте.
Как это работает?
C# 5 позволяет писать асинхронный код используя упрощенный синтаксис, и оставлять грязную работу компилятору. За кулисами, когда асинхронный метод скомпилирован, JIT генерирует машинный код. Примерно так это работает, если не вдаваться в подробности.Машинный код имеет метод, который содержит код оригинального метода, но он разбит на сегменты встроенные в switch-блоки. Когда метод выполняется - проверяется состояние и запускается соответствующий сегмент кода. При первом выполнении метода, состояние имеет какое-то начальное значение, следовательно выполнение начинается с оригинального метода. Напомню, что await применим только к методам типа Task/Task<T>
Это упрощенная версия описания того, как это работает.
Итого
C# 5 и .NET 4.5 огромный шаг вперед в упрощении асинхронного программирования и почти удачный. С введением новых ключевых слов (в C#) и многих асинхронных версий методов в FCL асинхронный код приближен к синхронному. Это также делает преобразование синхронного кода в асинхронный проще, что способствует отзывчивости приложений и счастью пользователей...Это был мой фривольный перевод статьи Asynchronous programming in C# using async and await
Немного абстрактный пример:
Enter your name:
TestName
Hi, TestName, Ellapsed 1000 ms
Хочется заметить, что создавать таску в методе AsyncHello не так уж обязательно, следующее определение метода также будет работать, да и читать проще:
Спасибо. Кратко и просто.
ОтветитьУдалитьЗачем так переводить ? Если переводите материал, пожалуйста переводите качественно и без translate.google.com.
ОтветитьУдалитьКомментариев был бы полезнее, если бы указали на мои ошибки
УдалитьБлять. Везде одно и тоже, что вы все прицепились к этому WebClient.
ОтветитьУдалитьСделали бы описание на своем кастомном async методе.
В топку.
Этот комментарий был удален автором.
УдалитьНу, например, этот код тупо не компилится. ))
УдалитьВезде одно и тоже, что вы все прицепились к этому WebClient.
УдалитьСделали бы описание на своем кастомном async методе.
В топку.
+1
http://pastebin.com/3FVwNBZb
ОтветитьУдалитьСпасибо за полезную статью.
ОтветитьУдалитьАлександр, думаю Вы имели в виду Task / Task вместо "Task/Task" и "Task или Task".
ОтветитьУдалитьБлин. движок блога удалил угловые скобки из комментария. Видимо, как из статьи - где должно было быть "Task Угловая-скобка-открыть Т Угловая-скобка-закрыть"
ОтветитьУдалитьВерно, спасибо большое!
УдалитьНевероятно, но фак :(