Размытие линий между асинхронными и обычными функциями в C # 5.0

10

В последнее время я не могу получить достаточно удивительного асинхронного паттерна C # 5.0. Где ты был всю мою жизнь?

Я просто в восторге от простого синтаксиса, но у меня есть одна небольшая сложность. Моя проблема в том, что асинхронные функции имеют совершенно другое объявление, чем обычные функции. Поскольку только другие асинхронные функции могут ожидать других асинхронных функций, когда я пытаюсь перенести некоторый старый блокирующий код в асинхронный режим, у меня возникает эффект домино функций, которые я должен преобразовать.

Люди называют это заражением зомби . Когда async откусит ваш код, он будет все больше и больше. Процесс портирования не сложен, он просто добавляет asyncобъявление и оборачивает возвращаемое значение Task<>. Но раздражает делать это снова и снова при переносе старого синхронного кода.

Мне кажется, было бы гораздо более естественным, если бы оба типа функций (асинхронная и обычная старая синхронизация) имели одинаковый синтаксис. Если бы это было так, портирование потребовало бы ноль усилий, и я мог бы безболезненно переключаться между этими двумя формами.

Я думаю, что это может сработать, если мы будем следовать этим правилам:

  1. Асинхронные функции больше не требуют asyncобъявления. Их возвращаемые типы не нужно было бы оборачивать Task<>. Компилятор сам идентифицирует асинхронную функцию во время компиляции и при необходимости автоматически выполняет перенос Task <>.

  2. Больше не нужно запускать вызовы асинхронных функций. Если вы хотите вызвать асинхронную функцию, вам нужно дождаться ее. В любом случае я почти не использую метод «забей и забудь», и все примеры сумасшедших условий гонки или тупиков всегда основаны на них. Я думаю, что они слишком запутаны и «не имеют отношения» к синхронному мышлению, которое мы пытаемся использовать.

  3. Если вы действительно не можете жить без огня и забыть, для него будет специальный синтаксис. В любом случае, это не будет частью простого унифицированного синтаксиса, о котором я говорю.

  4. Единственное ключевое слово, которое вам нужно для обозначения асинхронного вызова, это await. Если вы ожидаете, вызов асинхронный. Если вы этого не сделаете, вызов будет старым синхронным (помните, у нас больше нет возможности «забыть»).

  5. Компилятор автоматически идентифицирует асинхронные функции (поскольку у них больше нет специального объявления). Правило 4 делает это очень простым - если функция имеет awaitвызов внутри, она асинхронная.

Может ли это работать? или я что-то упустил? Этот унифицированный синтаксис является гораздо более гибким и может решить проблему заражения зомби.

Некоторые примеры:

// assume this is an async function (has await calls inside)
int CalcRemoteBalanceAsync() { ... }

// assume this is a regular sync function (has no await calls inside)
int CalcRemoteBalance() { ... }

// now let's try all combinations and see what they do:

// this is the common synchronous case - it will block
int b1 = CalcRemoteBalance();

// this is the common asynchronous case - it will not block
int b2 = await CalcRemoteBalanceAsync();

// choosing to call an asynchronous function in a synchronous manner - it will block
// this syntax was used previously for async fire-and-forget, but now it's just synchronous
int b3 = CalcRemoteBalanceAsync();

// strange combination - it will block since it's calling a synchronous function
// it should probably show a compilation warning though
int b4 = await CalcRemoteBalance();

Примечание: это продолжение интересного связанного обсуждения в SO

talkol
источник
3
Вы всегда ждете своих асинхронных операций? Пожалуйста, скажите мне, что вы не делаете это сразу после их увольнения ...
Джимми Хоффа
1
Кроме того, одна из замечательных особенностей асинхронности заключается в том, что вам не нужно делать это awaitнемедленно. Вы можете сделать что-то вроде var task = FooAsync(); Bar(); await task;. Как бы я сделал это в вашем предложении?
svick
3
ТАК ведет дискуссию? Где мой BFG-3000 ...
Роберт Харви
2
@talkol Вы думаете, что параллельное программирование непристойно? Это интересный взгляд, если не сказать больше, когда вы говорите async. Я думаю , что это одна из самых больших преимуществ async- await: что позволяет легко сочинять асинхронные операции (а не только в самой простой «начать, дождитесь, запуск B, ожидание B» путь). И для этого уже есть специальный синтаксис: он называется await.
svick
1
@svick ха-ха, теперь мы пошли туда :) Я не думаю, что параллельная прога является непристойной, но я думаю, что делать это с помощью async-await. Async-await - это синтаксический сахар для поддержания вашего синхронного состояния ума без расплаты за блокировку. Если вы уже думаете параллельно, я призываю вас использовать другой паттерн
talkol

Ответы:

9

На ваш вопрос уже дан ответ на вопрос, который вы связали.

Цель async / await - облегчить написание кода в мире, где много операций с высокой задержкой. Подавляющее большинство ваших операций не имеют большой задержки.

Когда впервые вышел WinRT , дизайнеры описывали, как они решили, какие операции будут асинхронными. Они решили, что все, что займет 50 мс или более, будет асинхронным, а оставшиеся методы будут обычными, не асинхронными.

Сколько методов нужно было переписать, чтобы сделать их асинхронными? Около 10 процентов из них. Остальные 90% не были затронуты вообще.

Эрик Липперт продолжает объяснять довольно существенными техническими подробностями, почему они решили не использовать подход «один размер подходит всем». Он в основном говорит , что asyncи awaitэто частичная реализация продолжения обходя стиль, и что оптимизация такого стиля , чтобы соответствовать всем случаям является трудной задачей.

Роберт Харви
источник
Обратите внимание на существенную разницу между вопросом SO и этим. ТАК спрашивает, почему бы не сделать все асинхронным. Здесь мы не предлагаем этого, мы предлагаем сделать 10% асинхронным, просто используя тот же синтаксис для этого, и все. Использование более близкого синтаксиса имеет то преимущество, что вы можете легче изменять, какие 10% являются асинхронными, не испытывая эффекта домино от этих изменений
talkol
Мне немного непонятно, зачем asyncпроизводить зомби. Даже если метод вызывает 10 других методов, разве вам не нужен только один из них asyncверхнего уровня?
Роберт Харви
6
Допустим, 100% моего текущего кода синхронизировано. Теперь у меня есть одна внутренняя конечная функция, которая запрашивает БД, которую я хотел бы изменить на асинхронную. Теперь, чтобы сделать его асинхронным, мне нужно, чтобы его вызывающий был асинхронным, а вызывающий - асинхронным, и так далее до верхнего уровня. Конечно, я говорю о случае, когда ожидается вся цепочка (чтобы сохранить синхронный дизайн кода или передать возвратные значения)
talkol