#c# #async-await
#c# #асинхронное ожидание
Вопрос:
Если у меня есть следующий метод:
public async Task<T> DoSomethingAsync<T>(Func<Task<T>> action)
{
// bunch of async code..then "await action()"
}
В чем разница между следующими двумя способами использования:
public async Task MethodOneAsync()
{
return await DoSomethingAsync(async () => await SomeActionAsync());
}
public async Task MethodTwoAsync()
{
return await DoSomethingAsync(() => SomeActionAsync());
}
Оба компилируются, оба работают, и предупреждений C # нет.
В чем разница (если есть)? Будут ли оба метода выполняться асинхронно, если их ожидает вызывающий?
Комментарии:
1. Разница в том, что
MethodTwo
элидыasync-await
иMethodOne
нет. Не тратьте время на споры о «разных» мнениях ;). У Стивена Клири есть статья об этом различии, исключающем Async и Await
Ответ №1:
Между ними нет функциональной разницы. Единственная разница заключается Task
в том, возвращается ли from SomeActionAsync напрямую или если оно ожидается. У Стивена Клири есть хороший пост в блоге об этом, и он рекомендует второй подход для этого тривиального случая.
Причина, по которой доступен первый подход, заключается в том, что у вас может быть нетривиальное лямбда-выражение, подобное этому:
public async Task MethodOneAsync()
{
return await DoSomethingAsync(async () => {
var i = _isItSunday ? 42 : 11;
var someResult = await SomeActionAsync(i);
return await AnotherActionAsync(someResult*i);
});
}
Таким образом, разница такая же, как разница между методом с этой сигнатурой public async Task<int> MyMethod
и этим public Task<int> MyMethod
Комментарии:
1. Что мешает пользователю перенести логику фигурных скобок в отдельный метод и использовать Async Await , это не причина, по которой здесь предоставляется async await , это полностью уменьшает значение Async-Await . Цель Async — Await — это всегда неблокирующая операция, при которой длительная операция может выполняться в фоновом режиме, поскольку OP подтвердил, что функционально два одинаковы, и они действительно есть, разница будет только в производительности для продолжительного метода, и именно поэтому предпочтительнее Async Await
2. Также позвольте мне добавить, что асинхронная операция на основе вычислений всегда выполняется в threadpool даже с Async -Await, другого способа нет, просто основной вызывающий поток свободен. Подлинная асинхронная операция — это в основном операции ввода-вывода, поскольку они не требуют никакого потока и действительно выполняются в фоновом режиме
3. итак, ответ, который не ожидает, проглотит исключение, и только внешний код должен будет обрабатывать? нужен кто-нибудь, чтобы просто перефразировать, что такое разница в двух словах.
4. @Seabizkit Какое исключение?
5. @JoakimM.H. read blog.stephencleary.com/2016/12/eliding-async-await.html
Ответ №2:
Короткий ответ
MethodOneAsync()
действительно асинхронный и должен использоваться, но MethodTwoAsync()
не является действительно асинхронным, поскольку он вызывает поток пула потоков
Длинный ответ
С целью тестирования и запуска я упростил ваш код следующим образом:
Выполнение из основного метода Linqpad следующим образом:
var resultTask = MethodOneAsync(); // Comment one the methods
resultTask.Result.Dump();
Фактический код
public async Task<int> DoSomethingAsync(Func<Task<int>> action)
{
return await Task.FromResult<int>(3);
}
public async Task<int> MethodOneAsync()
{
await Task.Delay(10);
return await DoSomethingAsync(async () => await Task.FromResult<int>(3));
}
public async Task<int> MethodOneAsync()
{
await Task.Delay(10);
return await DoSomethingAsync(() => Task.FromResult<int>(3));
}
Теперь я рассмотрел разницу IL generated
между двумя вызовами, и следующее является наиболее важным отличием:
Первый вызов с Async and Await
inside DoSomethingAsync
имеет следующий IL:
<>c.<MethodOneAsync>b__2_0:
IL_0000: newobj UserQuery <>c <<MethodOneAsync>b__2_0>d..ctor
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: ldarg.0
IL_0008: stfld UserQuery <>c <<MethodOneAsync>b__2_0>d.<>4__this
IL_000D: ldloc.0
IL_000E: call System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Int32>.Create
IL_0013: stfld UserQuery <>c <<MethodOneAsync>b__2_0>d.<>t__builder
IL_0018: ldloc.0
IL_0019: ldc.i4.m1
IL_001A: stfld UserQuery <>c <<MethodOneAsync>b__2_0>d.<>1__state
IL_001F: ldloc.0
IL_0020: ldfld UserQuery <>c <<MethodOneAsync>b__2_0>d.<>t__builder
IL_0025: stloc.1
IL_0026: ldloca.s 01
IL_0028: ldloca.s 00
IL_002A: call System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Int32>.Start<<<MethodOneAsync>b__2_0>d>
IL_002F: ldloc.0
IL_0030: ldflda UserQuery <>c <<MethodOneAsync>b__2_0>d.<>t__builder
IL_0035: call System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Int32>.get_Task
IL_003A: ret
Второй без Async and Await
имеет следующий код:
<>c.<MethodOneAsync>b__2_0:
IL_0000: ldc.i4.3
IL_0001: call System.Threading.Tasks.Task.FromResult<Int32>
IL_0006: ret
Помимо этого, первый имеет полный конечный машинный код для дополнительного async await
вызова, который ожидается.
Важные моменты:
- Для использования асинхронного вызова метода
async () => await SomeActionAsync()
, поскольку это истинное асинхронное выполнение и работает на портах завершения ввода-вывода - В другом случае он вызывает
Threadpool
поток для выполнения асинхронного метода, что не подходит для асинхронного выполнения
Я могу вставить полный IL, если требуется, чтобы понять разницу, но лучше всего оценить то же самое в Visual studio или LINQPad, чтобы понять нюансы
Комментарии:
1. @StephenCleary есть ли шанс, что вы могли бы прокомментировать этот ответ, пожалуйста?
2. @Pure.Krome, спасибо за ваш комментарий, голосование против никогда не является проблемой, но важно обоснование, чтобы я мог учиться и исправлять. Это никогда не должно быть причудливым.
3. Разница не в том, задействован или нет поток threadpool. Единственная разница заключается в том, возвращается ли задача из SomeActionAsync() напрямую или если она ожидается. Смотрите этот пост в блоге Стивена Клири.
4. Извините, но это неправильно. Независимо от того, является ли аргумент lambda для DoSomethingAsync асинхронным или нет, не имеет никакого отношения к блокировке или неблокированию. Давайте предположим, что DoSomethingAsync и SomeActionAsync являются правильно реализованными асинхронными методами: разница в том, возвращает ли лямбда-выражение задачу из SomeActinAsync напрямую или оборачивает результат в новую задачу и возвращает эту новую задачу. Сгенерированный IL отличается тем, что async lambda создает избыточный конечный автомат.
5.
Difference is in original example one of the implementation doesn't await the task within Func, which means calling thread is never relieved as expected in Async and Func will return only on completion of Task, thus blocking calling thread.
Это просто не так, как это работает. Задача просто возвращается. Чтобы заблокировать, вам нужно будет сделать что-то вродеSomeActionAsync().Result
.
Ответ №3:
Во-первых, ваш пример не имеет смысла, вы либо возвращаете что-то, либо нет, возврат результатов ожидаемой функции без возвращаемого типа будет ошибкой компилятора.
public async Task MethodOneAsync()
{
return await DoSomethingAsync(() => SomeActionAsync());
}
Во-вторых, это не имеет ничего общего с «истинной» асинхронностью, поскольку это было бы деталью реализации, которая не показана
В-третьих, единственное различие между любым из гипотетических примеров
await DoSomethingAsync(async () => await SomeActionAsync());
и
await DoSomethingAsync(() => SomeActionAsync());
Учитывая определение DoSomethingAsync
, в первом примере компилятор создаст дополнительную IAsyncStateMachine
реализацию вместо простой пересылки Task
. Т.е. Больше скомпилированного кода, больше IL, больше инструкций, и в этом примере, по-видимому, избыточно.
Однако, поскольку это всего лишь простой проход, нет другого кода, который будет выдавать, следовательно, дополнительный конечный автомат или try catch и Task.FromException
не нужен.
Реальные заметные различия возникли бы, если бы ваша подпись на самом деле была Action
вместо Func<Task>
того, чтобы создавать async void
заданную асинхронную лямбду, однако в вашем вопросе это не так.
Комментарии:
1. Ваш первый комментарий считает, что вы ошибаетесь в ожидании «void», вы всегда должны возвращать task, даже если это void . (я могу ошибаться). Ваш ответ пока самый интересный. Все еще не понимаю, в чем разница, кроме, возможно, другой оболочки task. Возможно, вы нашли что-нибудь еще, объясняющее разницу более подробно, кажется очень странным. это похоже на то, что вы передаете await как часть делегата, но это тоже не имеет особого смысла.
Ответ №4:
Конструкция Async / await вставляет некоторый код инфраструктуры, который полезен только в том случае, если после «await» есть какой-то код. В противном случае это, по сути, ничего не делает. Ваш код эквивалентен
public Task MethodThreeAsync()
{
return DoSomethingAsync(() => SomeActionAsync());
}
Все три метода являются «истинно асинхронными».
Комментарии:
1. Этот инфраструктурный код, сгенерированный
await
вызовом, также обеспечивает фрейм стека в отчетах об исключениях. Если вы полагаетесьException.StackTrace
на отладку, вам следует использоватьawait
, а не просто возвращать задачи.2. Это неверно, это не просто инфраструктурный код, как показано в моем ответе, это разница между истинной асинхронностью и пулом потоков, основанным на параллелизме, которые создают большую разницу для приложения и его работы
3. Не уверен, почему вы проголосовали против. Это в основном правильно, за исключением некоторых ошибок, связанных с обработкой ошибок.