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

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


    (для гуру: внутри корутины на yield'ах)

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

    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

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

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

    Средняя зарплата в IT

    113 000 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 5 065 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 17

      +3
      Эм. А что мешает компилировать C# 5 -> .NET 4?
        0
        Для того что бы это работало надо ставить заплатку на .NET 4.0 и к студии(либо переходить на VS2012).
          +2
          Это является проблемой?
            +3
            Да, это может быть большой проблемой для команды.
            Всей команде придется патчить VS2010 либо переходить на VS2012. На всех конечных компьютерах надо быть уверенным не только в .NET 4.0 но в KB2468871. На всех билд/тест машинах должна стоять заплатка. В зависимостях к проекту надо не забыть о Microsoft.Bcl.Async.

            О варианте с заплаткой и компиляцией из C# 5.0 под .NET 4.0 я уже писал в начале статьи.
            +3
            Сейчас заплатку отменили. Нужен только Microsoft.Bcl.Async и VS2012, но должно работать с AsyncCTP для 2010 студии.

            UPD: да, заплатка нужна.
          –1
          «тимлид грозит небесной карой за смену платформы»

          и правильно сделает! Следующий программист, кто будет работать с Вашим кодом, скажет: WTF!

          И да: KISS! KISS, BABY!
            +3
            Не очень ново. После выхода .Net 2.0 много подобного появлялось.

            Плюсы: красиво для того, кто понимает и автора. Минусы: некрасиво для остальных.

            Когда «остальные» погружаются в код с yield и разбираются настолько, что «понимают», то они вступают в секту приравниваются к автору, заодно дописывая пару красивостей к библиотеке и коду.
            А потом приходит тимлид/пм…
              0
              Да, не спорю, баян. Но, он делает код с множеством вложенных «продолжений» или колбеков проще для восприятия.

              Могу еще написать статью про нелегкое дело приведение/конвертации типов в .NET. Только в mscorlib и System.dll около 4х разных способов привести/сконвертировать один тип в другой. В бонус будет страшный классец(весь в генериках), который скрывает детали за одним интерфейсом: TypeConvert[Uri].From(«schema://localhost/»).
                +1
                Для меня yield — отложенное формирование IEnumerable

                Напишите, пожалуйста, «остальным» круг задач для которых нужно использовать yield кроме формирования IEnumerable в production коде.

                Ну если не IEnumerable, то это магия какая-то…
                  +1
                  Помимо генератора(основное предназначение) на yield можно собрать сопрограмму(выше) и конечный автомат. Это то что впомнилось в первую очередь, а так только фантазия(и тимлид) может ограничить область применения.
                +4
                Если ведется разработка desktop-приложения (Microsoft .NET Framework 4.0, Microsoft Visual Studio 2012), то для решения описанной задачи, можно использовать альтернативное решение от компании Microsoft в виде дополнительной библиотеки: описание проекта, пакет NuGet. Минус: дополнительная библиотека (~65KB). Плюс: код использования async/await полностью повторяет таковой для Microsoft .NET Framework 4.5, при переходе на новую версию просто убирается ссылка на библиотеку.
                  0
                  Штука достаточно мощная. Например, в новом питоне предлагается на таких «корутинах» написать целый стэк для написания вёб-серверов (и не только их). К сожалению, в C#5.0, как это видно, yield return сильно ограничен тем, что а) ничего не возвращает извне и б) не дружит с try {..} catch {..}.
                    0
                    У питоновцев уже есть технология continuation (stack-less Python) которая дает больше контроля над потоком исполнения и меньше проблем с продумыванием точек выхода/возврата через yield. А по поводу недостатков дотнетовского yield, то а) можно прокинуть «канал» с входящей информацией через аргументы б) try/catch работает, но с ограничениями на возможность yield внутри блока. и да, это не побороть
                    +3
                    Сразу вспомнилась библиотека Rx.NET.
                      0
                      Странный какой-то тимлид, если запрещает или не планирует переход на .NET 4.5.

                      Я сам тимлид, задача по переводу проекта на .NET 4.5 шла именно от меня.
                      Перевод прошел быстро и без серьезных проблем.

                      Потом запустили рефакторинг существующего кода по работе с асинхронностью, а так же перевод методов сервисов только на async.
                      Вы даже не представляете на сколько чище и понятнее стал код с переходом на async/await (особенно кода ушли IAsynResult, события т.д и).
                        –3
                        Поэтому я люблю С++ и сопутствующий ему Qt, в котором так хорошо пропагандированны сигнал-слоты (async вызовы — альтернатива колбекам)
                          +1
                          Асинхронный итератор Рихтера. Правда уже на тасках.
                          channel9.msdn.com/blogs/charles/jeffrey-richter-and-his-asyncenumerator — тут более подробно.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое