Чому ви коли-небудь «чекаєте» методу, а потім негайно допитуєте його повернену вартість?


24

У цій статті MSDN наведено наступний приклад коду (трохи відредагований для стислості):

public async Task<ActionResult> Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    Department department = await db.Departments.FindAsync(id);

    if (department == null)
    {
        return HttpNotFound();
    }

    return View(department);
}

FindAsyncМетод повертає Departmentоб'єкт по його ID, і повертає Task<Department>. Потім відділ негайно перевіряється, чи є він нульовим. Як я розумію, запит на значення Завдання таким чином заблокує виконання коду, поки значення з очікуваного методу не повернеться, фактично зробивши це синхронним викликом.

Навіщо ти коли-небудь це робив? Чи не було б простіше просто викликати синхронний метод Find(id), якщо ви все одно негайно заблокуєте?


Це може бути пов'язано з впровадженням. ... else return null;Тоді вам потрібно буде перевірити, чи справді метод знайшов потрібний вам департамент.
Джеремі Като

Я не бачу в asp.net, але в додатку Destop, роблячи це таким чином ви не заморожувати призначений для користувача інтерфейс
Rémi

Ось посилання, що пояснює концепцію очікування від дизайнера ... msdn.microsoft.com/en-us/magazine/hh456401.aspx
Джон Рейнор

Очікуйте, що варто подумати про ASP.NET, лише якщо вимикачі контактних потоків сповільнюють вас, або використання пам'яті у багатьох стеках потоків є проблемою для вас.
Ян

Відповіді:


24

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

Не зовсім.

Після виклику await db.Departments.FindAsync(id)завдання відсилається, а поточний потік повертається в пул для використання в інших операціях. Потік виконання блокується (як це було б незалежно від використання departmentвідразу після того, як я все зрозумів правильно), але сам потік вільний для використання іншими речами, поки ви чекаєте завершення операції поза машиною (і сигналізується портом події або завершення).

Якщо ви зателефонували, d.Departments.Find(id)то нитка все-таки сидить там і чекає відповіді, хоча більша частина обробки виконується в БД.

Ви ефективно звільняєте ресурси процесора під час зв’язування диска.


2
Але я подумав, що все, awaitщо було зроблено, - підписати на решті методу як продовження на тій самій потоці (є винятки; деякі методи асинхронізації створюють власну нитку), або підписують asyncметод як продовження на тій самій нитці та дозволяють залишився код, який потрібно виконати (як ви бачите, я не зрозуміла, як це asyncпрацює). Те, що ви описуєте, звучить більше як витончена формаThread.Sleep(untilReturnValueAvailable)
Роберт Харві

1
@RobertHarvey - він призначає його як продовження, але як тільки ви відправляєте завдання (і його продовження) на обробку, для запуску нічого не залишається. Не гарантується, що ви будете в одній темі, якщо ви не вкажете її (через ConfigureAwaitiirc).
Теластин

1
Дивіться мою відповідь ... продовження за замовчуванням повертається до початкового потоку.
Майкл Браун

2
Я думаю, що я бачу, чого мені тут не вистачає. Для того, щоб це дало будь-яку користь, рамка ASP.NET MVC повинна awaitзакликати public async Task<ActionResult> Details(int? id). В іншому випадку оригінальний дзвінок буде просто заблокований, чекаючи department == nullйого вирішення.
Роберт Харві

2
@RobertHarvey До моменту await ..."повернення" FindAsyncдзвінок закінчено. Ось що чекає. Це називається очікувати, тому що змушує ваш код чекати речей. (Але зауважте, що це не те саме, що змусити поточну нитку чекати на речі)
user253751

17

Я дуже ненавиджу, що жоден із прикладів не показує, як можна почекати кілька рядків, перш ніж чекати завдання. Розглянемо це.

Foo foo = await getFoo();
Bar bar = await getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(foo, bar);

Це такий тип коду, який заохочують приклади, і ви маєте рацію. У цьому мало сенсу. Основна тема дозволяє безкоштовно робити інші речі, як-от відповідь на введення інтерфейсу користувача, але реальна сила асинхронізації / очікування - я можу легко продовжувати робити інші речі, поки я чекаю завершення потенційно тривалого завдання. Код вище буде "блокований" і чекатиме виконання лінії друку, поки ми не отримаємо Foo & Bar. Не потрібно чекати, хоча. Ми можемо це обробити, поки будемо чекати.

Task<Foo> foo = getFoo();
Task<Bar> bar = getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(await foo, await bar);

Тепер, за допомогою переписаного коду, ми не зупиняємось і чекаємо своїх значень, поки не доведеться. Я завжди придивляюся до таких можливостей. Розумність, коли ми очікуємо, може призвести до значного покращення продуктивності. У нас сьогодні декілька ядер, можемо також їх використовувати.


1
Гм, це менше про ядра / потоки і більше про правильне використання асинхронних викликів.
Дедуплікатор

Ви не помиляєтесь @Deduplicator. Ось чому я цитував «блок» у своїй відповіді. Важко говорити з цими матеріалами, не згадуючи нитки, але, все ж таки визнаючи, може бути, а може і не бути багатопоточним.
RubberDuck

Я пропоную вам бути обережними з очікуванням всередині іншого виклику методу. Іноді компілятор неправильно розуміє це, і ви отримуєте дійсно дивне виняток. Зрештою, асинхронізація / очікування - це лише синтаксичний цукор, ви можете перевірити за допомогою sharplab.io, як виглядає створений код. Я спостерігав це кілька разів, і тепер я просто чекаю на один рядок вище дзвінка, де потрібен результат ... не потрібні ці головні болі.
виселено шум

"У цьому мало сенсу". - У цьому є багато сенсу. Випадки, коли «продовжуйте робити інші речі» застосовується саме тим самим методом, не такі поширені; набагато частіше, що ви просто хочете, awaitі натомість, щоб нитка робила зовсім інші речі.
Себастьян Редл

Коли я сказав: "У цьому мало сенсу" @SebastianRedl, я мав на увазі випадок, коли ви, очевидно, можете виконувати обидві завдання паралельно, а не бігати, чекати, бігати, чекати. Це набагато частіше, ніж ви думаєте, що це так. Б'юсь об заклад, якби ви оглянули кодову базу, знайдете можливості.
RubberDuck

6

Тож тут відбувається більше, що відбувається за лаштунками. Async / Await - синтаксичний цукор. Спочатку подивіться на підпис функції FindAsync. Він повертає завдання. Вже зараз ви бачите магію ключового слова, воно розкриває завдання в відділ.

Функція виклику не блокується. Що трапляється, це те, що присвоєння відділу та все, що слідує за ключовим словом, що очікує, потрапляють у закрите, а всі наміри та цілі передаються методу Task.ContinueWith (функція FindAsync автоматично виконується в іншому потоці).

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

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


1

Ні, це не повертається відразу. Очікування робить виклик методу асинхронним. Коли викликається FindAsync, метод Details повертається із завданням, яке не закінчено. Коли FindAsync закінчиться, він поверне свій результат у змінну відділу та відновить решту методу Details.


Ні, взагалі немає блокування. Він ніколи не потрапить до відділення == null, поки метод асинхронізації вже не буде закінчений.
Стів

1
async awaitяк правило, не створює нових потоків, і навіть якщо це було, вам все одно потрібно дочекатися значення цього відділу, щоб з’ясувати, чи воно недійсне.
Роберт Харві

2
Тому в основному те, що ви говорите, полягає в тому, що для того, щоб це взагалі принесло користь, заклик до нього public async Task<ActionResult> також повинен бути awaitвідредагований.
Роберт Харві

2
@RobertHarvey Так, у вас є ідея. Async / Await є по суті вірусом. Якщо ви "чекаєте" однієї функції, ви також повинні чекати будь-яких функцій, які її викликають. awaitне слід змішувати .Wait()або .Result, оскільки це може призвести до тупиків. Ланцюг асинхронізації / очікування зазвичай закінчується функцією з async voidпідписом, яка в основному використовується для обробників подій або для функцій, викликаних безпосередньо елементами інтерфейсу користувача.
KChaloux

1
@Steve - " ... так було б на власній нитці. " Ні, читання Завдання все ще не є потоками, і асинхронізація не є паралельною . Сюди входить фактичний вихід, що демонструє, що новий потік не створюється.
ToolmakerSteve

1

Мені подобається думати про "асинхронізацію", як про контракт, про контракт, в якому сказано: "Я можу виконати це асинхронно, якщо вам це потрібно, але ви також можете зателефонувати мені, як і будь-яка інша синхронна функція".

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

Task<Department> deptTask = db.Departments.FindAsync(id);

і після, скажімо, 10 рядків вниз функції, яку ви викликаєте

Department d = await deptTask;

отже, трактуйте це як асинхронну функцію.

Тобі вирішувати.


1
Це більше схоже на "Я залишаю за собою право обробляти повідомлення асинхронно, використовувати це для отримання результату".
Дедуплікатор

-1

"якщо ви все-таки негайно заблокуєте", відповідь - "Так". Тільки тоді, коли вам потрібна швидка реакція, чекати / асинхронізувати сенс. Наприклад, потік користувальницького інтерфейсу прийшов до дійсно асинхронного методу, потік інтерфейсу повернеться і продовжить слухати клацання кнопки, тоді як код нижче "очікувати" буде виправданий іншим потоком і нарешті отримає результат.


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