Асинхронное ожидание с методом, вызывающим внешний ресурс или возвращающим локальную строку

#c# #async-await

#c# #асинхронное ожидание

Вопрос:

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

У меня есть метод, который будет вызывать внешний ресурс с использованием HttpClient, если локальная переменная еще не заполнена, если локальная переменная определена, я возвращаю переменную.

Вот пример:

 1:  private static string foo;
2:  public static async Task<string> GetFooDataAsync()
3:  {
4:      var needToFetchFoo = string.IsNullOrWhiteSpace(foo);
5:      if (needToFetchFoo)
6:      {
7:          var httpResponse = await myHttpClient.GetFooAsync(params);
8:          foo = httpResponse.data;
9:      }
10:     return foo;
11: }
  

Меня беспокоит строка 10, в которой я возвращаю переменную? Должен ли я делать что-то вроде?

 return Task.FromResult(foo);
  

Влияет ли возврат переменной без Task.FromResult на то, что выполняет async await, и вызывает проблемы выше в моем стеке вызовов?

Комментарии:

1. Нет, вы не должны. Компилятор сделает эту работу за вас.

2. Возвращаемая вами строка foo помещается компилятором в задачу. Нет необходимости использовать Task.FromResult

3. Кстати, вы не вернули переменную — возвращается только значение переменной (как копия)

Ответ №1:

Код, который у вас есть, в порядке. При использовании async компилятор уже реализовал все необходимые процедуры для возврата задачи через сгенерированную компилятором реализацию an IAsyncStateMachine .

Как вы можете видеть в этом бессмысленном примере здесь

 public async Task<bool> DoSomething()
{
    return true;
}
  

Примерно переводится как

 [AsyncStateMachine(typeof(<DoSomething>d__0))]
public Task<bool> DoSomething()
{
    <DoSomething>d__0 stateMachine = default(<DoSomething>d__0);
    stateMachine.<>t__builder = AsyncTaskMethodBuilder<bool>.Create();
    stateMachine.<>1__state = -1;
    AsyncTaskMethodBuilder<bool> <>t__builder = stateMachine.<>t__builder;
    <>t__builder.Start(ref stateMachine);
    return stateMachine.<>t__builder.Task;
}
  

Вы бы использовали Task.FromResult and Task.FromException , когда не добавили async ключевое слово, и запускали синхронный код.

В этих случаях Task.FromResult возвращается завершенная задача и Task.FromException добавляет исключение к задаче так же, как фреймворк будет делать для исключений, генерируемых в async методе

 public Task<bool> DoSomeInterfaceAsync()
{
   try
   {
      // return a completed task
      return Task.FromResult(DoSomethingThatMightThrow());
   }
   catch (Exception e)
   {
      // Add the exception to the task 
      return Task.FromException<bool>(e);
   }   
}
  

Также интересно отметить, что в сгенерированном компилятором коде, показанном выше, есть MoveNext метод, который подтверждает Task.FromResult и Task.FromException , и его можно увидеть ниже с SetException помощью и SetException соответственно

 private void MoveNext()
{
    bool resu<
    try
    {
        result = true;
    }
    catch (Exception exception)
    {
        <>1__state = -2;
        <>t__builder.SetException(exception);
        return;
    }
    <>1__state = -2;
    <>t__builder.SetResult(result);
}
  

Исключение asynctaskmethod build.SetException

Помечает задачу как неудачную и привязывает указанное исключение к задаче.

asynctaskmethod Build.SetResult

Помечает задачу как успешно завершенную.

Ответ №2:

Логика кэширования немного испорчена. Во время выполнения http-запроса foo он остается незаселенным, и если GetFooDataAsync() в течение этого времени вызывается другой метод, http-запрос будет повторен. В идеале кэш должен заполняться только один раз.

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

 private static Task<string> foo = null;

private static async Task<string> GetFooDataAsync()
{
    async Task<string> GetFooDataInternal()
    {
        var response = await myHttpClient.GetFooAsync();
        return response.data;
    }

    if (foo == null) foo = GetFooDataInternal();
    return await foo;
}
  

Комментарии:

1. Спасибо, Джон, за указание на недостаток кэширования. Я действительно ценю это!

Ответ №3:

Если вы отметили метод как async , то вы должны вернуть результат, а не Task или Task<T> . Компилятор позаботится об остальном.

Task.FromResult обычно требуется, когда метод не помечен как async , но возвращаемый тип метода — Task or Task<T> .