Размытие линий между асинхронными и обычными функциями в 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


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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.