Какова цель "вернуться ждать" на С#? - программирование

Какова цель "вернуться ждать" на С#?

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

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?

4b9b3361

Ответ 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 вернул.