Зачем нам нужно ключевое слово async?

19

Я только начал играть с async / await в .Net 4.5. Изначально мне интересно узнать, почему необходимо ключевое слово async? Я прочитал объяснение, что это маркер, поэтому компилятор знает, что метод чего-то ждет. Но кажется, что компилятор должен быть в состоянии понять это без ключевого слова. Так что еще это делает?

ConditionRacer
источник
2
Вот действительно хорошая статья в блоге (серия), в которой подробно описывается ключевое слово в C # и связанные с ним функции в F #.
Пол
Я думаю, что это в основном маркер для разработчика, а не для компилятора.
CodesInChaos
8
Эта статья объясняет это: blogs.msdn.com/b/ericlippert/archive/2010/11/11/…
svick
@ Svick спасибо, это то, что я искал. Имеет смысл сейчас.
ConditionRacer

Ответы:

23

Здесь есть несколько ответов, и все они говорят о том, что делают асинхронные методы, но ни один из них не отвечает на вопрос, поэтому asyncон необходим в качестве ключевого слова, которое входит в объявление функции.

Это не «указание компилятору преобразовывать функцию особым образом»; awaitодин мог сделать это. Почему? Поскольку C # уже имеет другой механизм , где наличие специального ключевого слова в теле метода заставляет компилятор выполнить экстремум (и очень похож на async/await) преобразования на теле метода: yield.

За исключением того, что yieldэто не его собственное ключевое слово в C #, и понимание, почему объяснит asyncтакже. В отличие от большинства языков, которые поддерживают этот механизм, в C # вы не можете сказать, что yield value;вы должны сказать yield return value;вместо этого. Почему? Потому что он был добавлен в язык после того, как C # уже существовал, и было вполне разумно предположить, что кто-то где-то мог использовать yieldв качестве имени переменной. Но поскольку ранее существовавшего сценария, в котором <variable name> returnсинтаксически правильно не было, yield returnбыл добавлен язык, позволяющий вводить генераторы при сохранении 100% обратной совместимости с существующим кодом.

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

Мейсон Уилер
источник
3
Это в значительной степени то, о чем говорится в этой статье (изначально она была опубликована @svick в комментариях), но никто никогда не публиковал ее в качестве ответа. Только прошло два с половиной года! Спасибо :)
ConditionRacer
Не означает ли это, что в какой-то будущей версии требование об указании asyncключевого слова может быть отменено? Очевидно, что это будет разрыв BC, но можно подумать, что он затронет очень мало проектов и будет простым требованием к обновлению (то есть изменению имени переменной).
Quolonel Вопросы
6

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

и когда происходит что-то радикальное, обычно это ясно обозначают (мы извлекли урок из C ++)

чокнутый урод
источник
Так это действительно что-то для читателя / программиста, и не так много для компилятора?
ConditionRacer
@ Justin984, это определенно помогает сигнализировать компилятору о том, что должно произойти что-то особенное
трещотка
Думаю, мне любопытно, зачем это нужно компилятору. Разве он не может просто проанализировать метод и увидеть, что ожидание находится где-то в теле метода?
ConditionRacer
это помогает, когда вы хотите запланировать несколько asyncсекунд одновременно, чтобы они не запускались один за другим, но одновременно (если потоки доступны по крайней мере)
ratchet freak
@ Justin984: Не каждый возвращающий метод на Task<T>самом деле имеет характеристики asyncметода - например, вы могли бы просто пройти через некоторую бизнес / фабричную логику и затем делегировать другому возвращающемуся методу Task<T>. asyncметоды имеют тип возврата Task<T>в сигнатуре, но на самом деле не возвращают a Task<T>, они просто возвращают a T. Чтобы понять все это без asyncключевого слова, компилятор C # должен будет выполнить всестороннюю глубокую проверку метода, что, вероятно, значительно замедлит его и приведет к всевозможным неоднозначностям.
Aaronaught
4

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

Мировой инженер
источник
Я испытываю желание понизить голос, потому что, хотя первый абзац правильный, сравнение с прерываниями некорректно (по моему обоснованному мнению).
Пол
Я рассматриваю их как нечто аналогичное с точки зрения цели, но я уберу это для ясности.
Мировой инженер
На мой взгляд, прерывания больше похожи на события, чем на асинхронный код - они вызывают переключение контекста и завершаются до возобновления нормального кода (и нормальный код может полностью не знать о его выполнении), тогда как с асинхронным кодом метод может не завершено до возобновления вызывающего потока. Я вижу то, что вы пытались сказать, в отношении разных механизмов, требующих разных реализаций, но прерывания просто не вяжутся со мной, чтобы проиллюстрировать этот момент. (+1 к остатку, между прочим.)
Пол
0

Хорошо, вот мой взгляд на это.

Есть нечто, называемое сопрограммы , которое известно на протяжении десятилетий. («Кнут и Хоппер» -класс «на десятилетия») Они являются обобщениями подпрограмм , в том смысле , что они не только получают и освобождают управление в операторе запуска и возврата функции, но и делают это в определенных точках ( точках приостановки ). Подпрограмма - это сопрограмма без точек подвеса.

Их легко и просто реализовать с помощью макросов C, как показано в следующей статье о "protothreads". ( http://dunkels.com/adam/dunkels06protothreads.pdf ) Прочтите его. Я буду ждать...

Суть этого в том , что макросы создают большой switch, и caseметку в каждой точке подвески. В каждой точке приостановки функция сохраняет значение непосредственно следующей caseметки, чтобы она знала, где возобновить выполнение при следующем вызове. И это возвращает контроль звонящему.

Это делается без изменения видимого потока управления кодом, описанным в «protothread».

Теперь представьте, что у вас есть большой цикл, вызывающий все эти «протопотоки» по очереди, и вы одновременно выполняете «протопотоки» в одном потоке.

Этот подход имеет два недостатка:

  1. Вы не можете сохранять состояние в локальных переменных между возобновлениями.
  2. Вы не можете приостановить "protothread" от произвольной глубины вызова. (все точки подвеса должны быть на уровне 0)

Есть обходные пути для обоих:

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

И если бы у вас была поддержка компилятора для выполнения работы по перезаписи, которую выполняют макросы и обходной путь, ну, вы могли бы просто написать свой код прототипа, как вы и намеревались, и вставить точки приостановки с ключевым словом.

И это то, что asyncи awaitвсе: создание сопрограмм (без стеков).

Сопрограммы в C # являются объектами (универсального или неуниверсального) класса Task.

Я нахожу эти ключевые слова очень вводящими в заблуждение. Мое умственное чтение:

  • async как "надёжный"
  • await как "приостановить до завершения"
  • Task как "будущее ..."

Сейчас. Нам действительно нужно пометить функцию async? Помимо того, что он должен запускать механизмы переписывания кода, чтобы сделать функцию сопрограммой, он устраняет некоторые неоднозначности. Рассмотрим этот код.

public Task<object> AmIACoroutine() {
    var tcs = new TaskCompletionSource<object>();
    return tcs.Task;
}

Предполагая, что asyncэто не обязательно, это сопрограмма или нормальная функция? Должен ли компилятор переписать его как сопрограмму или нет? Оба могут быть возможны с различной возможной семантикой.

Laurent LA RIZZA
источник