Async-await в C# и Task.Wait() - как на самом деле дождаться окончания работы задачи?
Я учусь работать с sync-await в C#.
Хочу научиться следующему: 1) как связывать набор асинхронных задач в последовательность, в которой задачи выполняются друг за другом 2) как дожидаться выполнения асинхронной задачи 3) как запускать параллельно асинхронные задачи и тоже дожидаться окончания выполнения всех запущенных задач 4) в идеале - еще уметь делать из синхронных методов асинхронные и наоборот
Пробую довольно простую вещь: дождаться конца выполнения задачи.
Пишу следующий код, который почти целиком совпадет с кодом из примеров:
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace Async5
{
class Program
{
static DateTime begTime;
static void Main(string[] args) {
begTime = DateTime.Now;
Task t = new Task(DownloadPageAsync);
PrintMsgAndTime("task создан!");
t.Start();
t.Wait();
PrintMsgAndTime("DownloadPageAsync отработало!");
Console.ReadLine();
}
static async void DownloadPageAsync()
{
string url = "http://junecat.ru/Audio/";
PrintMsgAndTime("DownloadPageAsync(): начало");
using ( HttpClient httpClient = new HttpClient()) {
PrintMsgAndTime("HttpClient создан");
using ( HttpResponseMessage response = await httpClient.GetAsync(url) ) {
PrintMsgAndTime("response получен");
using (HttpContent content = response.Content) {
PrintMsgAndTime("content получен");
string result = await content.ReadAsStringAsync();
PrintMsgAndTime($"page size: {result.Length}");
}
}
}
}
static void PrintMsgAndTime(string msg) {
DateTime endTime = DateTime.Now;
TimeSpan ts = endTime - begTime;
Console.WriteLine($"{msg}, time spent:{(int)ts.TotalMilliseconds} ms.");
}
}
}
Я ожидаю, что t.Wait(); заставит программу подождать, пока вся процедура отработает. Но - это не так.
В результате запуска я получаю примерно такой вывод:
task создан!, time spent:1 ms.
DownloadPageAsync(): начало, time spent:12 ms.
HttpClient создан, time spent:60 ms.
DownloadPageAsync отработало!, time spent:89 ms.
response получен, time spent:200 ms.
content получен, time spent:201 ms.
page size: 59319, time spent:202 ms.
Здесь видно, что сначала задача отрапортовала об окончании, а потом уже - на самом деле закончилась.
Как я понимаю, причина вот в чем: у меня void метод. В результате этого как Task из этого метода возвражается Task первого же async метода. А мне нужно - вернуть Task самого глубоко вложенного async метода, и тогда, видимо, все будет работать так, как я ожидаю.
Вот только как бы это практически написать?
Ответы (2 шт):
Какой вопрос - такой ответ. Будьте более конкретны в вопросах - получите более конкретные ответы.
- как связывать набор асинхронных задач в последовательность, в которой задачи выполняются друг за другом
await task1;
await task2;
await task3;
- как дожидаться выполнения асинхронной задачи
await ваша_задача;
- как запускать параллельно асинхронные задачи и тоже дожидаться окончания выполнения всех запущенных задач
var task1 = задача_1;
var task2 = задача_2;
var task3 = задача_3;
await Task.WhenAll(task1, task2, task3);
- в идеале - еще уметь делать из синхронных методов асинхронные и наоборот
void mySyncMethod()
{
// sync code
}
Task myAsyncMethos()
{
await asynkTask;
}
Как я понимаю, причина вот в чем: у меня void метод. В результате этого как Task из этого метода возвражается Task первого же async метода
из void метода не возвращается ничего, так как он void - потому вы не можете его асинхронно ожидать, так как вы не знаете чего ожидать - метод то ничего не вернул. Поэтому не делайте асинхронных void методов, возвращаейте всегда Task.
Вот только как бы это практически написать?
static async Task DownloadPageAsync()
{
string url = "http://junecat.ru/Audio/";
PrintMsgAndTime("DownloadPageAsync(): начало");
using ( HttpClient httpClient = new HttpClient()) {
PrintMsgAndTime("HttpClient создан");
using ( HttpResponseMessage response = await httpClient.GetAsync(url) ) {
PrintMsgAndTime("response получен");
using (HttpContent content = response.Content) {
PrintMsgAndTime("content получен");
string result = await content.ReadAsStringAsync();
PrintMsgAndTime($"page size: {result.Length}");
}
}
}
}
static async Task Main(string[] args) {
begTime = DateTime.Now;
await DownloadPageAsync();
PrintMsgAndTime("DownloadPageAsync отработало!");
Console.ReadLine();
}
Мне долгое время не давала покоя такая особенность практически всех примеров, связанных с async-await:
когда в проекте начинает применяться асинхронщина, то все методы начинают возвращать Task<>.
я не хочу сказать, что это плохо.
Но бывают в жизни ситуации, когда все настолько просто, что еще один тип Вам не нужен. Потому что он будет просто лишний раз отвлекать на себя внимание.
Вот для таких случаев я оставлю здесь вот этот пример.
Это класс, который загружает web-старницу. При этом он использует внутри себя асинхронный HttpClient, но наружу он торчит только синхронным методом.
( я честно признаюсь, мне просто надоело каждый раз вспоминать, как это делается, а поиск по своим собственным вопросам-ответам на SO работает прекрасно )
Конечно же, от всех умных и хороших примеров он отличается только одной строчкой - t.Wait();
internal class GoodClass
{
internal string MakeAll()
{
// how to download file synchronously by async Http-client
const string url = "http://junecat.ru/Audio/";
var t = TmpMethod(url);
// t.Wait(); - как подсказали в комментариях, t.Result и так вызовет Wait()
string s = t.Result;
// write length for demo aims only!
Console.WriteLine($"length of s = {s.Length}");
return s;
}
async Task<string> TmpMethod(string url)
{
var httpClient = new HttpClient();
using (var stream = await httpClient.GetStreamAsync(url))
{
using (StreamReader sr = new StreamReader(stream))
{
return await sr.ReadToEndAsync();
}
}
}
}