Pull to refresh

Comments 24

Спасибо. Понятно и разложено по полочкам.

Вообще я очень благодарен команде C# за async/await, а то последнее время код стал превращаться в что-то jQuery-подобное, когда линейный код выглядел как 20 вложенных анонимных функций.
AsyncEnumerator — «наше всё» уже 2+ года, тем более, что для его использования достаточно C# 2.0 и .Net Framework 2.0.
А так, конечно, хочется уже и без «костылей» асинхронный код в линейном виде писать.
Вроде бы Рихтер приложил руку и к async/await. На моем опыте код становится в разы понятнее и лаконичнее.
Производительность: предпочитайте кэшировать сами задачи, нежели их данные
Несколько капитанский совет получился. Я с трудом представляю себе метод, который кэширует данные, но при этом возвращает задачи. Он что, возвращает только уже выполненные задачи? Хотелось бы увидеть работающий пример плохого и неправильного кода.

Производительность: понимайте, как await сохраняет состояние
Замечание 1. Если вы декларируете переменную, она сохранится в объекте, хранящем состояние. Это может привести к тому, что объекты будут оставаться в памяти дольше, чем вы бы могли ожидать.
Не может. Поля объекта, относящиеся к выходящим из области видимости переменным, обNULLяются автоматически.

Замечание 2. Но если вы не станете декларировать переменную, а использовать значение Async вызова вместе с await, переменная попадёт во внутренний стек:

Результат вызова await всегда попадает в поле — по другому компилятор просто не умеет.
Вы заблуждаетесь, это верно только если метод не использует async/await.
А если использует, то вычисленные значения попадают не в локальную переменную метода (или что вы имели в виду под «полем»), а в стек стейт-машины в его поле:
private object <>t__stack;


Здесь описано как это работает, а в ILSpy вы можете посмотреть какой код генерирует компилятор (если отключите декомпиляцию async/await).
А если использует, то вычисленные значения попадают не в локальную переменную метода (или что вы имели в виду под «полем»), а в стек стейт-машины в его поле


Вы издеваетесь или как? Именно это поле я и имел в виду. При чем тут локальная переменная метода?

PS Проверил в ILSpy, нет там обNULLения, я ошибся. Странно, где же я его видел?
Ну я то думал, что внутренний стек == поле <>t__stack; и тогда не понятно, что же вы имели в виду?

Однако, автор же, очевидно, просто имел в виду то, что если вы используете await в параметре, то значение будет храниться в этом поле, а если присваиваете результат await переменной — для неё компилятор сделает собственное поле в стейт-машине (что будет быстрее работать, нежели расширять Tuple-ы со значениями для параметров и класть их в t__stack в рантайме).
Значит у вас плохая фантазия. Кешировать данные — более очевидно чем таски. Ибо долго достаются именно данные, а не создаются таски.
Очевидно-то более, но я не представляю, как будет работать параллельное получение данных с кэшированием этих самых данных. Вот и хочется посмотреть на получившийся код того человека, который не догадался до кэширования тасков.
Я кажется понял о чем вы. Да, таски будут в любом случае, разница лишь в том что ляжет в кеш, сами таски или их результат.
В качестве сборника простых понятных примеров рекомендую вот эту вещь (код можно менять на лету).
«Лучшие практики» — это вообще что значит? Какой это язык?
Best practices — лучшие приёмы, секреты мастерства.
Предпочитайте async/await вместо Task

Но внутри MyWebService.FetchDataAsync() будет return Task.Factory.StartNew(() =>…

Как-то не получается вместо. Получается вместе. Может кто-нибудь прояснит этот момент?
Вы одновременно и правы и не совсем правы :)
Вообще говоря async/await работает поверх awaitable типов, самый очевидный из которых Task, и в самом простом случае конечно async/await это _надстройка_ над Task.
Но awaitить можно не только Task, а любой тип реализующий await контракт (наличие метода GetAwaiter возвращающего объект с property IsCompleted, методом OnCompleted(Action) и методом T GetResult())

Это довольно важно в случае с overlapped IO, когда для ожидания результата IO не нужна отдельная нить( а Task работает именно с нитями через ThreadPool)
Task совершенно не обязательно работает с нитями.
Задачу можно создать при помощи TaskCompletionSource, тогда она не будет требовать отдельной нити для выполнения.
ConfigureAwait(false) надо использовать в любом коде, который не нуждается в доступе к потоку UI, но запускается из потока UI, иначе управление будет постоянно возвращаться к нему и часть работы будет выполняться в потоке UI. Запустите профайлер Visual Studio и убедитесь в этом.

Короче, я не использую ConfigureAwait(false) только в асинхронных обработчиках событий от UI.
Интересная статья, но машинный перевод удручает.
Не переводите то что переводить не нужно. Reentrance, SynchronizationContext (это банально класс), SecurityContext (тоже)
Машина состояний это в данном случае конечный автомат, контекст ASP.NET — это Request Context.

"… по выбору функциональсти, которая должна быть асинхронной." — choosing methods that should be async. Тут странная замена методов на функциональность.

"… я предпочитаю async/await вместо создания цепочек задач посредством Task." — I prefer async/await to setting up Task chains
Вы как то легко вставляете слово «задач». Ведь тут речь не о цепочках задач, а о цепочках Task. Первое — это нечто абстрактное, а второе — технический прием.
Вот и я местами чтобы понять о чем речь переводил в голове дословно обратно на английский.
Используйте async /await только для тех мест, которые могут длиться «долго»

Не совсем корректно. async/await подходит для IO-bound операций, т.е. работа с сетью, диском, где код ничего не делает, а сидит и ждет ответа на IO порт. Для CPU-bound задач, которые тоже могут длиться «долго» и потребляют ресурсы постоянно, разумнее использовать параллелизацию и фоновые потоки.
Фоновой поток вполне можно обернуть в задачу.
Само собой. Но использование одного только async/await для CPU-bound кода делу несильно поможет.
Sign up to leave a comment.

Articles