Какова цель "вернуться ждать" на С#?
Есть ли любой сценарий, где такой метод записи:
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 в этом случае и прямое возвращение Task будет функционально эквивалентным. Зачем добавлять дополнительные накладные расходы дополнительного слоя await
?
Ответы
Ответ 1
Существует один подлый случай, когда return
в обычном методе и return await
в async
метод ведет себя по-другому: в сочетании с using
(или, в более общем смысле, любой return await
в блоке try
),.
Рассмотрим эти две версии метода:
Task<SomeResult> DoSomethingAsync()
{
using (var foo = new Foo())
{
return foo.DoAnotherThingAsync();
}
}
async Task<SomeResult> DoSomethingAsync()
{
using (var foo = new Foo())
{
return await foo.DoAnotherThingAsync();
}
}
Первый метод будет Dispose()
объект Foo
, как только метод DoAnotherThingAsync()
вернется, что, вероятно, задолго до его завершения. Это означает, что первая версия, вероятно, глючит (потому что Foo
находится слишком рано), а вторая версия будет работать нормально.
Ответ 2
Если вам не нужен async
(т.е. вы можете напрямую вернуть Task
), тогда не используйте async
.
Есть несколько ситуаций, когда return await
полезен, например, если у вас есть две асинхронные операции:
var intermediate = await FirstAsync();
return await SecondAwait(intermediate);
Подробнее о производительности async
см. в статье Стивена Тууба статьи MSDN и видео по теме.
Обновление: Я написал сообщение , которое более подробно описано.
Ответ 3
Единственная причина, по которой вы хотите это сделать, - это если в предыдущем коде есть еще один await
или если вы каким-то образом обработаете результат перед его возвратом. Другой способ, которым это может происходить, - это try/catch
, который изменяет обработку исключений. Если вы ничего не делаете, тогда вы правы, нет причин добавлять накладные расходы на создание метода async
.
Ответ 4
В другом случае вам может потребоваться дождаться результата:
async Task<IFoo> GetIFooAsync()
{
return await GetFooAsync();
}
async Task<Foo> GetFooAsync()
{
var foo = await CreateFooAsync();
await foo.InitializeAsync();
return foo;
}
В этом случае GetIFooAsync()
должен ждать результата GetFooAsync
, потому что тип T
отличается между двумя методами, а Task<Foo>
не может быть напрямую назначен Task<IFoo>
. Но если вы ожидаете результата, он просто становится Foo
, который напрямую присваивается IFoo
. Затем метод async просто переупаковывает результат внутри Task<IFoo>
и уходит.
Ответ 5
Выполняя иначе простой метод "thunk", async создает в памяти состояние async state machine, а не async - нет. Хотя это часто может указывать на использование неасинхронной версии, поскольку она более эффективна (это правда), это также означает, что в случае зависания у вас нет доказательств того, что этот метод задействован в "стеке возврата/продолжения", что иногда затрудняет понимание повесы.
Итак, да, когда perf не критичен (и обычно это не так), я брошу async на все эти thunk-методы, чтобы у меня была машина состояния async, чтобы помочь мне диагностировать зависания позже, а также помочь обеспечить что если эти методы thunk будут развиваться со временем, они обязательно вернут сбитые задачи вместо throw.
Ответ 6
Это также смущает меня, и я чувствую, что в предыдущих ответах не учитывался ваш фактический вопрос:
Зачем использовать конструкцию return wait, когда вы можете напрямую вернуть Task из внутреннего вызова DoAnotherThingAsync()?
Ну, иногда вам действительно нужен Task<SomeType>
, но в большинстве случаев вам действительно нужен экземпляр SomeType
, то есть результат задачи.
Из вашего кода:
async Task<SomeResult> DoSomethingAsync()
{
using (var foo = new Foo())
{
return await foo.DoAnotherThingAsync();
}
}
Человек, незнакомый с синтаксисом (например, я), может подумать, что этот метод должен возвращать Task<SomeResult>
, но поскольку он помечен async
, это означает, что его фактический тип возврата SomeResult
.
Если вы просто используете return foo.DoAnotherThingAsync()
, вы должны вернуть задачу, которая не будет компилироваться. Правильный способ - вернуть результат задачи, поэтому return await
.
Ответ 7
Если вы не используете return await, вы можете испортить трассировку стека во время отладки или когда она печатается в журналах исключений.
Когда вы возвращаете задачу, метод выполнил свою задачу и вышел из стека вызовов. Когда вы используете return await
вы уходите в стек вызовов.
Например:
Стек вызовов при использовании await: A ожидает задачи от B => B ожидает задачи от C
Стек вызовов, когда не используется await: A ожидает задачу от C, которую B вернул.