Они смеются над твоими колбеками или async/await «для бедных»

от автора

У вас проект на .NET 4.0 и вам надоела «лапша» из колбеков? Вы бы хотели использовать async/await в своем проекте, но тимлид грозит небесной карой за смену платформы? Если установка патча на фреймворк и студию для вас являются допустимым решением, то вам сюда. Если нет, то существует другое решение.


Немного теории

Async-метод это сопрограмма, которая выходит на каждом await, и восстанавливается с этой точки по завершению ожидания(автор знает, что выполнение не всегда прерывается на await и что «ожидать» можно не только экземпляры Task). Итак, начиная со второй версии дотнета можно легко создавать сопрограммы с помощью ключевого слова yield. Этим инструментом мы и воспользуемся.

Концепт

Хочется писать как на C# 5.0, и при этом не ставить никаких языковых расширений. К сожалению так не получится. Но есть вот такой вариант:

private IEnumerable Login(...) {              // ...              // get user id, if not specified             if (string.IsNullOrEmpty(uid))             {                 var getUserIdTask = сlient.GetUserId(...); yield return getUserIdTask; // await                 uid= getUserIdTask.Result.uid;             }              // login             var loginTask = сlient.Login(...); yield return loginTask; // await             var sessionId = loginTask.Result.SessionId;              // getting user's profile             var getUserInfoTask = сlient.GetUserInfo(...); yield return getUserInfoTask; // await             var userInfo = getUserInfoTask.Result;                         // ...                          yield return userInfo; // return } 

Всё что возвращается через yield return и не является наследником Task считается результатом исполнения async-метода.

Реализация

Код лежит тут.
Механизм работы простой:

  1. Создаем корневой Task и возвращаем вызывающему
  2. Вращаем итератор
  3. Если вернулся Task, то ждем его завершения через ContinueWith с переходом к шагу №2
  4. Если вернулась ошибка, то выставляем Exception для коневого Task
  5. Если вернулось значение, то завершаем коневой Task с данным результатом
  6. Если итератор кончился, то завершаем коневой Task со стандартным результатом

Во всех вариантах завершения на итераторе будет вызван Dispose, что приведет к освобождению ресурсов в блоках using и try/finally.

Начать новую асинхронную задачу можно вызвав метод FromIterator:

private IEnumerable Login(...) { ... }  Task loginTask = TaskUtils.FromIterator(this.Login(...)); // или с возвращаемым значением Task<UserInfo> loginTask = TaskUtils.FromIterator<UserInfo>(this.Login(...));  

Опционально можно указать:

  • state — состояние, которое попадет в Task.AsyncState
  • creationFlags — TaskCreationOptions для корневой задачи. Установка TaskCreationOptions.LongRunning говорит о том что вы очень не хотите блокировать текущий поток и вся работа должна быть выполнена в другом потоке
  • cancellationToken — токен прерывания процесса исполнения async-метода, передается вглубь везде, где это возможно
Заключение

Плюсы:

  • Компактненько и чисто
  • Можно не бояться за unmanaged ресурсы, using, try/catch работают
  • Не требует патчей и доп. библиотек
  • Будет работать в Mono
Минусы:

  • Async-метод возвращает IEnumerable, а не Task. Иногда приходится создавать дополнительный метод, который возвращает Task.
  • «Ожидать» можно только экземпляры Task
  • Ошибки «ожидаемых» задач нельзя обработать через try/catch(можно через ContinueWith). При ошибке в ожидаемой задаче, у корневой задачи выставляется Exception и поток исполнения больше не посещает async-метод.
  • Внутренний класс реализации(TaskBuilder) не порождает ссылок на себя, кроме тех случаев когда подписывается на ContinueWith в ожидаемых задачах, и есть вероятность сборки его GC

Большую часть минусов можно побороть тем или иным способом.

Ошибки по тексту можно направлять в ЛС.
Исходный код.

ссылка на оригинал статьи http://habrahabr.ru/post/174799/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *