Вернуть с await при переносе старого асинхронного шаблона в TaskCompletionSource?
Я изучаю C# Asnc-await pattern и в настоящее время читаю Concurrency in C# Cookbook от S. Cleary.
Он обсуждает перенос старых асинхронных шаблонов без TAP с помощью TaskCompletionSource (TCS) в конструкции TAP. Чего я не понимаю, так это почему он просто возвращает свойство Task объекта TCS, а не ожидает его TCS.Task?
Вот пример кода:
Старый метод для переноса - DownloadString(...):
public interface IMyAsyncHttpService
{
void DownloadString(Uri address, Action<string, Exception> callback);
}
Оборачиваем его в конструкцию TAP:
public static Task<string> DownloadStringAsync(
this IMyAsyncHttpService httpService, Uri address)
{
var tcs = new TaskCompletionSource<string>();
httpService.DownloadString(address, (result, exception) =>
{
if (exception != null)
tcs.TrySetException(exception);
else
tcs.TrySetResult(result);
});
return tcs.Task;
}
Теперь почему бы просто не сделать это так:
public static async Task<string> DownloadStringAsync(
this IMyAsyncHttpService httpService, Uri address)
{
var tcs = new TaskCompletionSource<string>();
httpService.DownloadString(address, (result, exception) =>
{
if (exception != null)
tcs.TrySetException(exception);
else
tcs.TrySetResult(result);
});
return await tcs.Task;
}
Есть ли функциональная разница между ними? Второй не более естественный?
5 ответов
Отметив его как асинхронный, компилятор сгенерирует предупреждения о том, что его следует ожидать в ожидании этого метода.
Вы не должны отмечать свой собственный метод как async
чтобы получить предупреждение "Задача не ожидала". Следующий код генерирует одинаковое предупреждение для вызовов обоих T
а также U
:
static async Task Main(string[] args)
{
Console.WriteLine("Done");
T();
U();
Console.WriteLine("Hello");
}
public static Task T()
{
return Task.CompletedTask;
}
public static async Task U()
{
await Task.Yield();
return;
}
Всякий раз, когда вы оказываетесь с методом, содержащим только один await
и что это последнее, что он делает (за исключением возможного возврата ожидаемого значения), вы должны спросить себя, какое значение оно добавляет. Помимо некоторых различий в обработке исключений, это просто добавление дополнительного Task
в смесь.
await
как правило, способ указать "У меня нет никакой полезной работы, чтобы сделать сейчас, но будет, когда этот другой Task
закончен ", что, конечно, не соответствует действительности (у вас нет другой работы, чтобы сделать позже). Поэтому пропустите await
и просто вернуть то, что вы ожидали бы вместо этого.
Ваша версия строго более сложна - вместо того, чтобы просто возвращать задачу, вы делаете метод асинхронным и ожидаете задачу, которую вы могли бы просто вернуть.
Прочтите мой ответ, почему возврат задания не является хорошей идеей: какова цель "возврата в ожидании" в C#?
По сути, вы нарушаете свой стек вызовов, если не используете await.
Ребята, спасибо за полезные комментарии.
В то же время я прочитал источники, на которые ссылаются здесь, а также исследовал этот вопрос: под влиянием https://blog.stephencleary.com/2016/12/eliding-async-await.html я пришел к выводу, что его лучше Рекомендуется включать асинхронное ожидание по умолчанию даже в синхронных методах цепочки асинхронных функций и исключать асинхронное ожидание только тогда, когда обстоятельства явно указывают на то, что метод потенциально не будет вести себя иначе, чем ожидается от сценариев асинхронного метода на границе.
Например: синхронный метод является коротким, простым и не имеет внутри операций, которые могут вызвать исключение. Если синхронный метод генерирует исключение, вызывающая сторона получит исключение в вызывающей строке, а не в строке, в которой ожидается задача. Это явно отличается от ожидаемого поведения.
Например, передача параметров вызова на следующий уровень без изменений - это ситуация, в которой imo позволяет опустить async-await.
Есть одно тонкое практическое отличие (кроме версии с await
медленнее бегать).
В первом примере, если DownloadString
выдает исключение (вместо вызова делегата, с которым вы передаете exception
установить), то это исключение будет всплывать через ваш вызов DownloadStringAsync
,
Во втором случае исключение упаковано в Task
вернулся из DownloadStringAsync
,
Итак, предполагая, что DownloadString
выдает это исключение (и никаких других исключений не возникает):
Task<string> task;
try
{
task = httpService.DownloadStringAsync(...);
}
catch (Exception e)
{
// Catches the exception ONLY in your first non-async example
}
try
{
await task;
}
catch (Exception e)
{
// Catches the exception ONLY in your second async example
}
Вы, вероятно, не заботитесь о различии - если вы просто напишите:
await httpService.DownloadStringAsync(...);
Вы не заметите разницу.
Опять же, это происходит только в том случае, если DownloadString
сам метод кидает. Если вместо этого он вызывает делегата, вы даете ему exception
установите значение, тогда между двумя вашими случаями заметной разницы нет.