#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>
.