Async-wait Task.Run vs HttpClient.GetAsync - программирование
Подтвердить что ты не робот

Async-wait Task.Run vs HttpClient.GetAsync

Я новичок в async С# 5. Я пытаюсь понять разницу между этими двумя реализациями:

Реализация 1:

private void Start()
{
    foreach(var url in urls)
    {
        ParseHtml(url);
    }
}

private async void ParseHtml(string url)
{
    var query = BuildQuery(url); //BuildQuery is some helper method
    var html = await DownloadHtml(query);
    //...
    MyType parsedItem = ParseHtml(html);
    SaveTypeToDB(parsedItem);
}

private async Task<string> DownloadHtml(string query)
{
    using (var client = new HttpClient())
    try
    {
        var response = await client.GetAsync(query);
        return (await response.Content.ReadAsAsync<string>());
    }
    catch (Exception ex)
    {
        Logger.Error(msg, ex);
        return null;
    }
}

Реализация 2:

private void DoLoop()
{
    foreach(var url in urls)
    {
        Start(url);
    }
}

private async void Start(url)
{
    await Task.Run( () => ParseHtml(url)) ;
}

private void ParseHtml(string url)
{
    var query = BuildQuery(url); //BuildQuery is some helper method
    var html = DownloadHtml(query);
    //...
    MyType parsedItem = ParseHtml(html);
    SaveTypeToDB(parsedItem);
}

private string DownloadHtml(string query)
{
    using (var client = new WebClient())
    {
        try
        {
            return client.DownloadString(query);
        }
        catch (Exception ex)
        {
            Logger.Error(msg, ex);
            return null;
        }
    }
}

Я предпочел бы использовать вторую реализацию, поскольку для моего кода мне потребуется меньше асинхронных подписи. Я пытаюсь понять, в чем преимущество использования класса HttpClient против новой задачи и ее ожидания?

Есть ли разница между двумя реализациями?

4b9b3361

Ответ 1

Я предпочел бы использовать вторую реализацию, поскольку для моего кода мне потребуется меньше "асинхронных" подписей.

Это звучит как очень странное оправдание. Вы пытаетесь выполнить фундаментально "несколько асинхронно" - так почему бы не сделать это ясно?

Есть ли разница между двумя реализациями?

Совершенно верно. Вторая реализация свяжет поток в то время как WebClient.DownloadString блоки, ожидая завершения запроса. Первая версия не имеет заблокированных потоков - она ​​полагается на продолжение, которое нужно выполнить, когда запрос заканчивается.

Кроме того, рассмотрите ваш вызов Logger.Error. В асинхронной версии это будет выполняться в контексте исходного кода вызова. Так что если это, скажем, пользовательский интерфейс Windows Forms, вы все равно будете в потоке пользовательского интерфейса, и вы можете получить доступ к элементам пользовательского интерфейса и т.д. Во второй версии вы будете выполнять поток потока пулов и вам понадобятся для возврата к потоку пользовательского интерфейса для обновления пользовательского интерфейса.

Обратите внимание, что ваш метод async void почти наверняка не должен быть async void. Вы должны сделать метод async return void только для соответствия сигнатурам обработчика событий. Во всех остальных случаях верните Task - таким образом вызывающий может видеть, когда ваша задача закончена, обрабатывать исключения и т.д.

Также обратите внимание, что вам не нужно использовать HttpClient для асинхронности - вместо этого вы можете использовать WebClient.DownloadStringTaskAsync, поэтому ваш окончательный метод может стать:

private async Task<string> DownloadHtml(string query)
{
    using (var client = new WebClient())
    {
        try
        {
            return await client.DownloadStringTaskAsync(query);
        }
        catch (Exception ex)
        {
            Logger.Error(msg, ex);
            return null;
        }
    }
}

Ответ 2

Для серверных приложений async заключается в минимизации количества заблокированных потоков, которые у вас есть: повышения эффективности пула потоков и, возможно, обеспечения масштабирования вашей программы для большего числа пользователей.

Для клиентских приложений, где вам вряд ли понадобится следить за количеством потоков, async обеспечивает относительно простой способ поддерживать работу вашего UI при выполнении операций ввода-вывода.

Он сильно отличается от Task.Run под капотом.

Ответ 3

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

Вторые реализации запускают задачу, но вы ждете ее завершения, не делая ничего другого. Таким образом, вы не выиграете.

Вы не описываете свою среду, но если у вас есть пользовательский интерфейс, который должен поддерживать отзывчивость, то реализация метода 1 в порядке, за исключением того, что ваш Start() не объявлен async и не ждет:

private async Task StartAsync()
{
    foreach (var url in urls)
    {
        await ParseHtml(url)
    }
}

Вы можете вызвать это из обработчика событий следующим образом:

private async void OnButton1_clicked(object sender, ...)
{
   await StartAsync();
}

Примечание: ParseHtml предшествует ожиданию. Следующий html будет проанализирован после завершения предыдущего анализа. Однако, поскольку синтаксический анализ является асинхронным, вызывающий поток (поток пользовательского интерфейса?) Сможет выполнять другие действия, такие как отвечать на ввод пользователя.

Однако, если ваша функция parseHTML может работать одновременно, предпочтительным будет следующий код и, вероятно, быстрее:

private async Task StartAsync()
{
    var tasks = new List<Task>()
    foreach (var url in urls)
    {
        tasks.Add(ParseHtml(url));
    }
    // now you have a sequence of Tasks scheduled to be run, possibly simultaneously.
    // you can do some other processing here
    // once you need to be certain that all tasks are finished await Task.WhenAll(...)
    await Task.WhenAll(tasks);
    // Task.WhenAls(...) returns a Task, hence you can await for it
    // the return of the await is a void
}
  • Если вы вызываете функцию, которая возвращает задачу, вы можете продолжать делать другие вещи во время выполнения задачи или ждать завершения задачи и использовать результаты задачи.
  • если вы ждете, ваш код останавливается, но ваши абоненты продолжают обработку, пока они не ожидают завершения вашей задачи.
  • вы можете ждать только в своей процедуре, если ваша процедура асинхронна.
  • Функции async могут быть вызваны другими асинхронными функциями или вызовом Task.Run(() = > ...) или, если это необходимо: Task.Factory.StartNew(() = > ...)
  • Вместо void функция async возвращает Task
  • Вместо TResult функция async return Задача <TResult >
  • Единственным исключением является обработчик события: объявите его async и return void.
  • Если вам нужно завершить задачу, просто ждите выполнения задачи.
  • Возврат ожидания - это TResult.