Навіщо нам потрібне ключове слово async?


19

Я щойно почав грати з async / ждати в. Net 4.5. Мені спочатку цікаво, чому потрібне ключове слово async? Я пояснив, що я прочитав, що це маркер, тому компілятор знає, що метод щось чекає. Але здається, що компілятор повинен мати можливість це зрозуміти без ключового слова. То що ще робить?


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ключового слова можна скасувати? Очевидно, що це буде перерва в БК, але, можливо, можна вважати, що це вплине на дуже мало проектів і буде простою вимогою до оновлення (тобто зміни назви змінної).
питання

6

він змінює метод із звичайного методу на об'єкт із зворотним викликом, що вимагає абсолютно іншого підходу для генерації коду

а коли трапляється щось таке драстичне, зазвичай це чітко позначається (ми дізналися цей урок на C ++)


Так це справді щось для читача / програміста, а не стільки для компілятора?
ConditionRacer

@ Justin984 це, безумовно, допомагає сигналізувати компілятору щось особливе, що повинно відбутися
ratchet freak

Я думаю, мені цікаво, чому компілятор це потребує. Не могли б просто проаналізувати метод і побачити, що очікування знаходиться десь у тілі методу?
ConditionRacer

це допомагає, коли ви хочете запланувати кілька asyncs одночасно, щоб вони не працювали один за одним, але одночасно (якщо нитки доступні принаймні)
ratchet freak

@ Justin984: Не кожен метод повернення Task<T>насправді має характеристики asyncметоду - наприклад, ви можете просто запустити деяку логіку бізнесу / фабрики і потім делегувати інший метод повернення Task<T>. asyncметоди мають тип повернення Task<T>у підписі, але насправді не повертають a Task<T>, вони просто повертають a T. Аби розібратися у всьому цьому без цьогоasync ключового слова, компілятору C # довелося б зробити всілякі глибокі перевірки методу, що, ймовірно, досить сповільнить його та призведе до всіляких неясностей.
Aaronaught

4

Вся ідея з такими ключовими словами, як "асинхроніка" або "небезпечна", полягає в тому, щоб усунути неоднозначність щодо того, як слід поводитися з модифікованим кодом. У разі ключового слова async він повідомляє компілятору трактувати модифікований метод як щось, що не потрібно повертати негайно. Це дозволяє потоці, де цей метод використовується, щоб продовжуватись, не чекаючи результатів цього методу. Це ефективно оптимізація коду.


Мені спокуса спростувати, оскільки, хоча перший абзац правильний, порівняння з перериваннями є невірним (на мою поінформовану думку).
пав

Я вважаю їх дещо аналогічними за призначенням, але я зніму це для наочності.
Світовий інженер

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

0

Гаразд, ось моя думка про це.

Існує щось, що називається корутинами, про що відомо десятиліттями. ("Класс і Хоппер" -клас "десятиліттями") Вони є узагальненням підпрограм , таких як вони не тільки отримують і звільняють контроль при операції запуску і повернення функції, але і роблять це в конкретних точках ( точках припинення ). Підпрограма - це підпрограма без точок припинення.

Їх ГРУТИ ЛЕГКО реалізувати за допомогою макросів C, як показано у наступній статті про "прототрафах". ( http://dunkels.com/adam/dunkels06protothreads.pdf ) Прочитайте. Я почекаю...

Суть цього полягає в тому, що макроси створюють велику switchта caseмітку в кожній точці підвіски. У кожній точці призупинення функція зберігає значення негайно наступногоcase мітки, так що вона знає, де відновити виконання наступного разу, коли вона буде викликана. І це повертає контроль абоненту.

Це робиться без зміни видимого потоку управління кодом, описаного в "прототраді".

Уявіть собі, що у вас є великий цикл, що викликає по черзі всі ці "прототони", і ви одночасно виконуєте "прототради" на одному потоці.

Цей підхід має два недоліки:

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

Для обох є вирішення:

  1. Усі локальні змінні повинні бути підтягнуті до контексту прототрадування (контекст, який вже потрібен тому, що прототрафа повинна зберігати свою наступну точку відновлення)
  2. Якщо ви відчуваєте, що вам дійсно потрібно зателефонувати ще з прототрафа, "нерестуйте" прототипу дитини та призупиніть до завершення роботи дитини.

І якби у вас була підтримка компілятора, щоб виконати роботу з перезапису, яку виконують макроси та обхідний шлях, ви можете просто написати свій код протокредиту так, як ви плануєте, і вставити пункти призупинення з ключовим словом.

А це що asyncіawait : створення (без стоп) розслідувань.

Співпраці в C # переробляються як об'єкти (загального або негенеричного) класу Task .

Я вважаю ці ключові слова дуже оманливими. Моє розумове читання:

  • async як "підвісний"
  • await як "призупинити до завершення"
  • Task як "майбутнє ..."

Тепер. Чи дійсно нам потрібно позначити функцію async? Крім того, що він повинен запускати механізми перезапису коду, щоб перетворити функцію на супровід, він вирішує деякі неясності. Розглянемо цей код.

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

Якщо припустити, що asyncце не є обов'язковим, це поправка чи нормальна функція? Чи повинен перекладач переписати його як супровід чи ні? І те й інше може бути можливим з різною можливою семантикою.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.