Как мы должны использовать async await? - программирование

Как мы должны использовать async await?

Я смотрел на то, как использовать async await, но я не совсем понял, когда у нас есть несколько методов, вызывающих друг друга. Должны ли мы всегда использовать await или использовать await только тогда, когда мы действительно готовы использовать результат?

Так, например, мы должны сделать это так:

async Task<string[]> FooAsync()
{
    var info = await Func1();
    return info.split('.');
}

async Task<string> Func1()
{
    return await Func2();
}

async Task<string> Func2()
{
    return await tcpClient.ReadStringAsync();
}

Или вот так:

async Task<string[]> FooAsync()
{
    var info = await Func1();
    return info.split('.');
}

Task<string> Func1()
{
    return Func2();
}

Task<string> Func2()
{
    return tcpClient.ReadStringAsync();
}

В примере 1, мы должны всегда использовать await в каждом методе?
Или же
В примере 2 мы должны использовать await только для самого верхнего метода, когда начинаем использовать результат?

4b9b3361

Ответ 1

Каждый раз, когда вы вызываете await он создает кусок кода для объединения переменных, захватывает контекст синхронизации (если это применимо) и создает продолжение в IAsyncStateMachine.

По сути, возврат Task без ключевого слова async даст вам небольшую эффективность во время выполнения и сэкономит кучу IL. Обратите внимание, что функция Async в .NET уже имеет много оптимизаций. Также обратите внимание (и важно), что возвращение Task в операторе using, скорее всего, вызовет исключение "Уже удалено".

Здесь вы можете сравнить разницу между IL и сантехникой.

Таким образом, если ваш метод просто перенаправляет Task и ничего от нее не хочет, вы можете просто удалить ключевое слово async и вернуть Task напрямую.

Более того, бывают случаи, когда мы делаем больше, чем просто пересылка, и происходит ветвление. Именно Task.CompletedTask вступают в игру Task.FromResult и Task.CompletedTask чтобы помочь разобраться с логикой того, что может возникнуть в методе. Т.е. если вы хотите дать результат (тут же) или вернуть Task, которое выполнено (соответственно).

И наконец, шаблон Async и Await Pattern имеет небольшие различия при работе с исключениями. Если вы возвращаете Task, вы можете использовать Task.FromException<T>, чтобы совать любое исключение на возвращаемых Task как async метод обычно будет делать.

Бессмысленный пример

public Task<int> DoSomethingAsync(int someValue)
{
   try
   {
      if (someValue == 1)
         return Task.FromResult(3); // Return a completed task

      return MyAsyncMethod(); // Return a task
   }
   catch (Exception e)
   {
      return Task.FromException<int>(e); // Place exception on the task
   }
}

Короче говоря, если вы не совсем понимаете, что происходит, просто await; накладные расходы будут минимальными. Однако, если вы понимаете подзаголовки о том, как вернуть результат задачи, выполненную задачу, поместить исключение в задачу или просто переслать. Вы можете сэкономить немного CIL и дать своему коду небольшой выигрыш в производительности, async ключевое слово async возвращающее задачу напрямую, и обходя IAsyncStateMachine.


Примерно в это же время я бы посмотрел пользователя и автора Qaru Стивена Клири и мистера Параллеля Стивена Тауба. У них есть множество блогов и книг, посвященных исключительно Async и Await Pattern, всем подводным камням, правилам кодирования и множеству дополнительной информации, которую вы наверняка найдете интересной.

Ответ 2

Оба варианта допустимы, и у каждого варианта есть свои сценарии, в которых он более эффективен, чем другой.

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

public async Task Execute()
{
    try
    {
        await RunAsync();
    }
    catch (Exception ex)
    {
        // Handle thrown exception
    }
}

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

Но есть сценарии, в которых вы должны ждать выполнения задачи, даже если вы ничего не делаете с результатом и не хотите обрабатывать возможные исключения

public Task<Entity> GetEntity(int id)
{
    using (var context = _contextFactory.Create())
    {
        return context.Entities.FindAsync(id);
    }
}

В приведенном выше сценарии FindAsync может возвратить FindAsync задачу, и эта задача будет немедленно возвращена вызывающей стороне и удаляется объект context созданный в операторе using.
Позже, когда вызывающая сторона будет "ждать" для задачи, будет сгенерировано исключение, поскольку она попытается использовать уже удаленный объект (context).

public async Task<Entity> GetEntity(int id)
{
    using (var context = _contextFactory.Create())
    {
        return await context.Entities.FindAsync(id);
    }
}

И традиционно ответы об Async Await должны включать ссылку на блог Стивена Клири
Eliding Async и Await

Ответ 3

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

В вашем примере Func1() и Func2() не обрабатывают возвращаемые значения вызванных асинхронных функций, поэтому не стоит их ждать.

Ответ 4

Когда вы используете await, код будет ждать завершения асинхронной функции. Это должно быть сделано, когда вам нужно значение из асинхронной функции, как в этом случае:

int salary = await CalculateSalary();

...

async Task<int> CalculateSalary()
{
    //Start high cpu usage task
    ...
    //End high cpu usage task
    return salary;
}

Если бы вы не использовали ожидание, это произойдет:

int salary = CalculateSalary().Result;

...

async Task<int> CalculateSalary()
{
    //Start high cpu usage task
    ... //In some line of code the function finishes returning null because we didn't wait the function to finish
    return salary; //This never runs
}

Await означает ожидание завершения этой асинхронной функции.

Используйте его для своих нужд, ваши случаи 1 и 2 будут давать тот же результат, пока вы ждете, когда вы назначите информационное значение, код будет безопасным.

Источник: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/index

Ответ 5

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

Ответ 6

Я полагаю, что 2-ой будет делать, потому что ожидание ожидает возвращаемого значения. Так как он ожидает, пока Func1() вернет значение, Func1() уже выполняет Func2() которая возвращает значение.