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 шт):

Автор решения: tym32167

Какой вопрос - такой ответ. Будьте более конкретны в вопросах - получите более конкретные ответы.

  1. как связывать набор асинхронных задач в последовательность, в которой задачи выполняются друг за другом
await task1;
await task2;
await task3;
  1. как дожидаться выполнения асинхронной задачи
await ваша_задача;
  1. как запускать параллельно асинхронные задачи и тоже дожидаться окончания выполнения всех запущенных задач
var task1 = задача_1;
var task2 = задача_2;
var task3 = задача_3;

await Task.WhenAll(task1, task2, task3);
  1. в идеале - еще уметь делать из синхронных методов асинхронные и наоборот
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();
        }
→ Ссылка
Автор решения: S.H.

Мне долгое время не давала покоя такая особенность практически всех примеров, связанных с 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();
                }
            }
        }
    }
→ Ссылка