Могут ли методы async иметь дорогой код перед первым "ожиданием"?

Неужели плохо иметь дорогостоящий код в начале метода асинхронного вызова, прежде чем вызывается первый await? Должен ли этот код быть обернут с помощью TaskEx.Run вместо?

public async Task Foo()
{
    // Do some initial expensive stuff.
    // ...

    // First call to an async method with await.
    await DoSomethingAsync;
}

Ответ 1

Как говорит Рид, это действительно зависит от контекста. Код должен выполняться в какой-то момент, но в зависимости от контекста он может быть запущен в потоке пула потоков, а не в критическом.

Вместо использования Task.Run я использовал бы TaskEx.Yield:

public async Task Foo()
{
    await TaskEx.Yield();
    // Do expensive stuff
}

Насколько мне известно, это в основном способ немедленного возврата к вызывающему абоненту, но позволяет сразу же планировать остальную часть асинхронного метода. Если у вас что-то вроде потока пользовательских интерфейсов Windows Forms, нет смысла делать это, так как вы вернетесь к потоку пользовательского интерфейса (и запускаете дорогостоящий код там) сразу, но это будет иметь смысл, если вы находитесь в контексте где текущий поток не должен блокироваться, но продолжения выполняются в другом потоке.

Ответ 2

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

Если возможно, я бы порекомендовал попробовать иметь как можно меньше "дорогого" кода, ведущего к первому ожиданию. Использование Task.Run (или TaskEx.Run в CTP) для переноса дорогого кода или переноса дорогого кода в его собственный асинхронный метод (на котором вы могли бы await) были бы полезны в этом случае.