Есть ли какой - либо сценарий , где метод записи , как это:
public async Task<SomeResult> DoSomethingAsync()
{
// Some synchronous code might or might not be here... //
return await DoAnotherThingAsync();
}
вместо этого:
public Task<SomeResult> DoSomethingAsync()
{
// Some synchronous code might or might not be here... //
return DoAnotherThingAsync();
}
будет иметь смысл?
Зачем использовать return await
конструкцию, если вы можете напрямую вернуться Task<T>
из внутреннего DoAnotherThingAsync()
вызова?
Я вижу код return await
во многих местах, думаю, я мог что-то пропустить. Но, насколько я понимаю, отсутствие использования ключевых слов async / await в этом случае и прямой возврат Задачи будет функционально эквивалентным. Зачем добавлять дополнительные накладные расходы на дополнительный await
слой?
Ответы:
Есть один хитрый случай, когда
return
в обычном методе иreturn await
вasync
методе ведут себя по-разному: в сочетании сusing
(или, в более общем случае, любымreturn await
вtry
блоке).Рассмотрим эти две версии метода:
Первый метод будет объект как только возвращает метод, который, вероятно , задолго до того , как на самом деле завершается. Это означает, что первая версия, вероятно, глючит (потому что удаляется слишком рано), тогда как вторая версия будет работать нормально.
Dispose()
Foo
DoAnotherThingAsync()
Foo
источник
foo.DoAnotherThingAsync().ContinueWith(_ => foo.Dispose());
Dispose()
возвращаетсяvoid
. Вам нужно что-то вродеreturn foo.DoAnotherThingAsync().ContinueWith(t -> { foo.Dispose(); return t.Result; });
. Но я не знаю, зачем вам это делать, когда вы можете использовать второй вариант.{ var task = DoAnotherThingAsync(); task.ContinueWith(_ => foo.Dispose()); return task; }
. Вариант использования довольно прост: если вы находитесь на .NET 4.0 (как и большинство), вы все равно можете написать асинхронный код таким образом, который будет прекрасно работать из приложений 4.5.Foo
только после того, как возвращенныеTask
завершены, что мне не нравится, потому что он излишне вводит параллелизм.Если вам не нужно
async
(то есть вы можете вернутьTask
напрямую), то не используйтеasync
.В некоторых ситуациях
return await
это полезно, например, если вам нужно выполнить две асинхронные операции:Подробнее о
async
производительности читайте в статье Стивена Тауба и видео MSDN на эту тему.Обновление: я написал сообщение в блоге, которое входит в гораздо более подробную информацию.
источник
await
это полезно во втором случае? Почему бы не сделатьreturn SecondAwait(intermediate);
?return SecondAwait(intermediate);
в этом случае я не достигну цели? Я думаю, чтоreturn await
и здесь избыточно ...await
в первой строке, вы должны использовать его и во второй.async
версия будет использовать меньше ресурсов (потоков), чем ваша синхронная версия.SecondAwait
является `строка ', появляется сообщение об ошибке:" CS4016: поскольку это асинхронный метод, возвращаемое выражение должно иметь тип' строка ', а не "Task <string>'".Единственная причина, по которой вы захотите это сделать, - это то, что
await
в более раннем коде есть какие-то другие , или если вы каким-то образом манипулируете результатом перед его возвратом. Другим способом, которым это может происходить, являетсяtry/catch
изменение, которое обрабатывает исключения. Если вы ничего не делаете, тогда вы правы, нет никаких причин добавлять накладные расходы на создание методаasync
.источник
return await
нужно (вместо того, чтобы просто возвращать задачу дочернего вызова), даже если в более раннем коде есть еще какие-то ожидания . Не могли бы вы дать объяснение?async
то как вы будете ждать первого задания? Вам нужно пометить метод так, какasync
будто вы хотите использовать любые ожидающие. Если метод помечен как,async
и у вас есть болееawait
ранний код, то вам нужно выполнитьawait
вторую асинхронную операцию, чтобы он имел правильный тип. Если вы только что удалили,await
он не скомпилируется, так как возвращаемое значение не будет правильного типа. Поскольку метод являетсяasync
результатом, он всегда заключен в задачу.async
методе вы не возвращаете задачу, вы возвращаете результат задачи, который затем будет перенесен.Task<Type>
явно, в то время какasync
диктует возвратType
(во что превратился бы сам компиляторTask<Type>
).async
это просто синтаксический сахар для явного продолжения. Вам не нужноasync
ничего делать, но при выполнении любой нетривиальной асинхронной операции работать с ней значительно проще. Например, код, который вы предоставили, на самом деле не распространяет ошибки так, как вы этого хотите, и делать это правильно даже в более сложных ситуациях становится намного сложнее. В то время как вам никогда не нужноasync
, ситуации, которые я описываю, это то, где это добавляет ценность для его использования.Еще один случай, вам может потребоваться дождаться результата:
В этом случае
GetIFooAsync()
необходимо дождаться результата,GetFooAsync
так как типT
отличается между двумя методами иTask<Foo>
не может быть напрямую назначенTask<IFoo>
. Но если вы ждете результата, он просто становится ,Foo
который находится непосредственно присваиваемыйIFoo
. Затем асинхронный метод просто переупаковывает результат внутри,Task<IFoo>
и все готово.источник
Task<>
является инвариантным.Создание в противном случае простого метода «thunk» async создает в памяти асинхронный конечный автомат, а не асинхронный - нет. Хотя это часто может указывать людям на использование неасинхронной версии, поскольку она более эффективна (что верно), это также означает, что в случае зависания у вас нет никаких доказательств того, что этот метод задействован в «стеке возврата / продолжения» что иногда затрудняет понимание зависания.
Так что да, когда perf не критичен (и обычно это не так), я включу асинхронность во всех этих методах thunk, чтобы у меня был асинхронный конечный автомат, чтобы помочь мне диагностировать зависания позже, а также чтобы гарантировать, что если эти Методы thunk постоянно развиваются, и они будут возвращать ошибочные задачи вместо броска.
источник
Если вы не используете return await, вы можете испортить трассировку стека во время отладки или когда она печатается в журналах исключений.
Когда вы возвращаете задачу, метод выполнил свою задачу, и он вышел из стека вызовов. Когда вы используете
return await
вы оставляете его в стеке вызовов.Например:
Стек вызовов при использовании await: A ожидает задачи от B => B ожидает задачи от C
Стек вызовов, когда не используется await: A ожидает задачу от C, которую B вернул.
источник
Это также смущает меня, и я чувствую, что предыдущие ответы упустили из виду ваш актуальный вопрос:
Ну, иногда вы действительно хотите
Task<SomeType>
, но в большинстве случаев вам нужен экземплярSomeType
, то есть результат задачи.Из вашего кода:
Человек, незнакомый с синтаксисом (например, я), может подумать, что этот метод должен возвращать a
Task<SomeResult>
, но, поскольку он помеченasync
, это означает, что его фактический тип возврата равенSomeResult
. Если вы просто используетеreturn foo.DoAnotherThingAsync()
, вы будете возвращать задачу, которая не будет компилироваться. Правильный способ - вернуть результат задачи, поэтомуreturn await
.источник
var task = DoSomethingAsync();
вам дадут задание, а неT
async/await
вещь. Насколько я понимаюTask task = DoSomethingAsync()
, покаSomething something = await DoSomethingAsync()
оба работают. Первый дает вам задачу, а второй, благодаряawait
ключевому слову, дает вам результат задачи после ее завершения. Я мог бы, например, иметьTask task = DoSomethingAsync(); Something something = await task;
.