Зачем вам когда-либо «ждать» метод, а затем немедленно запрашивать его возвращаемое значение?

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
Джон Рейнор
Стоит подумать об Await только в ASP.NET, если переключатели контактов потоков замедляют работу или использование памяти во многих стеках является проблемой для вас.
Ян

Ответы:

24

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

Не совсем.

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

Если вы позвонили, d.Departments.Find(id)поток действительно сидит и ждет ответа, хотя большая часть обработки выполняется в БД.

Вы эффективно освобождаете ресурсы процессора, когда диск привязан.

Telastyn
источник
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вызов закончен. Это то, что ждет. Он называется await, потому что он заставляет ваш код ждать чего-то. (Но учтите, что это не то же самое, что заставлять текущий поток ждать чего-либо)
user253751
17

Я действительно ненавижу, что ни один из примеров не показывает, как можно подождать несколько строк, прежде чем ждать задания. Учти это.

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

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

doStuff(foo, bar);

Это тот код, который поощряют примеры, и вы правы. В этом мало смысла. Он освобождает основной поток для выполнения других задач, таких как реагирование на ввод пользовательского интерфейса, но реальная сила async / await в том, что я могу легко продолжать выполнять другие действия, ожидая завершения потенциально долго выполняемой задачи. Приведенный выше код «заблокирует» и будет ждать выполнения строки печати, пока мы не получим Foo & Bar. Там нет необходимости ждать, хотя. Мы можем обработать это, пока мы ждем.

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

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

doStuff(await foo, await bar);

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

Резиновая утка
источник
1
Хм, это меньше о ядрах / потоках и больше о правильном использовании асинхронных вызовов.
Дедупликатор
Вы не ошиблись @ Дедупликатор. Вот почему я процитировал «блок» в своем ответе. Трудно говорить об этом без упоминания потоков, но, тем не менее, признавая, что может быть или не быть задействовано многопоточность.
RubberDuck
Я предлагаю вам быть осторожным с ожиданием внутри другого вызова метода. Иногда компилятор неправильно понимает это ожидание, и вы получаете действительно странное исключение. Ведь async / await - это просто синтаксический сахар, вы можете проверить с sharplab.io, как выглядит сгенерированный код. Я наблюдал это несколько раз, и теперь я просто жду одну строку над вызовом, где нужен результат ... эти головные боли не нужны.
выселенный шум
«В этом мало смысла». - В этом много смысла. Случаи, когда «продолжай делать другие вещи» применимы в том же самом методе, не столь распространены; гораздо более распространено, что вы просто хотите awaitи позволяете потоку делать совершенно разные вещи.
Себастьян Редл
Когда я сказал «в этом мало смысла» @SebastianRedl, я имел в виду случай, когда вы, очевидно, могли бы запускать обе задачи параллельно, а не запускать, ждать, запускать, ждать. Это гораздо чаще, чем вы думаете. Бьюсь об заклад, если вы посмотрите вокруг своей базы кода, вы найдете возможности.
RubberDuck
6

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

Вызывающая функция не блокируется. Что происходит, так это то, что присвоение отделу и всему, что следует за ключевым словом await, помещается в замыкание и для всех намерений и целей передается методу Task.ContinueWith (функция FindAsync автоматически выполняется в другом потоке).

Конечно, за кулисами происходит нечто большее, потому что операция перенаправляется обратно в исходный поток (так что вам больше не нужно беспокоиться о синхронизации с пользовательским интерфейсом при выполнении фоновой операции), а в случае вызова функции Async ( и вызывается асинхронно) то же самое происходит в стеке.

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

Майкл Браун
источник
1

Нет, это не возвращается сразу. Ожидание делает вызов метода асинхронным. Когда вызывается FindAsync, метод Details возвращает задание, которое не выполнено. Когда FindAsync завершит работу, он вернет свой результат в переменную отдела и возобновит работу остальной части метода Details.

Стив
источник
Нет, там вообще нет блокировки. Он никогда не попадет в отдел == null, пока асинхронный метод не будет завершен.
Стив
1
async awaitкак правило, не создает новые потоки, и даже если это произошло, вам все равно нужно подождать, пока значение этого отдела не выяснит, является ли оно нулевым.
Роберт Харви
2
Итак, в основном вы говорите, что для того, чтобы это принесло какую-либо пользу, public async Task<ActionResult> необходимо такжеawait
Роберт Харви
2
@RobertHarvey Да, у тебя есть идея. Async / Await по сути вирусный. Если вы «ожидаете» одну функцию, вы должны также ожидать любые функции, которые ее вызывают. awaitНе следует смешивать с .Wait()или .Result, так как это может вызвать взаимные блокировки. Цепочка async / await обычно заканчивается функцией с async voidсигнатурой, которая в основном используется для обработчиков событий или для функций, вызываемых непосредственно элементами пользовательского интерфейса.
KChaloux
1
@Steve - " ... так что это будет в отдельном потоке. " Нет, чтение Задачи по-прежнему не являются потоками и асинхронность не является параллельной . Это включает фактический вывод, демонстрирующий, что новый поток не создан.
ToolmakerSteve
1

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

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

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

и после, скажем, 10 строк вниз функции, которую вы вызываете

Department d = await deptTask;

следовательно, рассматривая это как асинхронную функцию.

Тебе решать.

user734028
источник
1
Это больше похоже на «Я оставляю за собой право обрабатывать сообщение асинхронно, используйте это для получения результата».
Дедупликатор
-1

«если вы собираетесь немедленно заблокировать», ответ «Да». Только когда вам нужен быстрый ответ, ожидание / асинхронность имеют смысл. Например, поток пользовательского интерфейса переходит к действительно асинхронному методу, поток пользовательского интерфейса вернется и продолжит прослушивать нажатие кнопки, в то время как код ниже «await» будет выполнен другим потоком и, наконец, получит результат.

user10200952
источник
1
Что дает этот ответ, а другие нет?