it-roy-ru.com

Какова цель "возврата ждать" в C #?

Есть ли любой сценарий, где метод написания такой:

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?

182
TX_

Есть один хитрый случай, когда 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 удаляется слишком рано), тогда как вторая версия будет работать нормально.

142
svick

Если вам не нужна async (т.е. вы можете вернуть Task напрямую), то не используйте async.

В некоторых ситуациях return await полезен, например, если у вас есть две асинхронные операции:

var intermediate = await FirstAsync();
return await SecondAwait(intermediate);

Подробнее о производительности async см. В статье Стефана Тауба статья MSDN и видео .

Update: Я написал запись в блоге , которая более детально описана.

72
Stephen Cleary

Единственная причина, по которой вы захотите это сделать, - это если в предыдущем коде есть какой-то другой await, или если вы каким-то образом манипулируете результатом перед его возвратом. Другим способом, которым это может происходить, является try/catch, который изменяет способ обработки исключений. Если вы ничего не делаете, то вы правы, нет никаких причин добавлять накладные расходы на создание метода async.

23
Servy

Еще один случай, когда вам может потребоваться дождаться результата:

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. Затем асинхронный метод просто переупаковывает результат внутри Task<IFoo> и все готово.

12
Andrew Arnott

Создание в противном случае простого метода «thunk» async создает в памяти асинхронный конечный автомат, а не асинхронный - нет. Хотя это часто может указывать людям на использование неасинхронной версии, поскольку она более эффективна (что верно), это также означает, что в случае зависания у вас нет никаких доказательств того, что этот метод задействован в «стеке возврата/продолжения» что иногда затрудняет понимание зависания. 

Так что да, когда perf не критичен (и обычно это не так), я включу асинхронность во всех этих методах thunk, чтобы у меня был асинхронный конечный автомат, чтобы помочь мне диагностировать зависания позже, а также чтобы гарантировать, что если эти Методы thunk постоянно развиваются, и они будут возвращать ошибочные задачи вместо броска.

3
Andrew Arnott

Это также смущает меня, и я чувствую, что предыдущие ответы упустили из виду ваш актуальный вопрос:

Зачем использовать конструкцию return await, если вы можете напрямую вернуть 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.

2
heltonbiker

Если вы не используете return await, вы можете испортить трассировку стека во время отладки или когда она печатается в журналах исключений.

Когда вы возвращаете задачу, метод выполнил свою задачу, и он вышел из стека вызовов. Когда вы используете return await, вы оставляете его в стеке вызовов.

Например:

Стек вызовов при использовании await: A в ожидании задания из B => B в ожидании задания из C

Стек вызовов, когда нет с использованием await: A ожидает задания от C, которое B вернул.

0
haimb