Async-await Task.Run vs HttpClient.GetAsync

时间:2012-11-23 22:40:38

标签: c# asynchronous task-parallel-library async-await c#-5.0

我是c#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类与使用新任务并等待它的好处是什么?

两种实现之间有什么区别吗?

3 个答案:

答案 0 :(得分:28)

  

我宁愿使用第二个实现,因为它在我的代码中需要更少的“异步”签名。

听起来像非常奇怪的理由。你试图从根本上“稍微异步”地执行 - 所以为什么不说清楚呢?

  

两种实现之间有什么区别吗?

绝对。第二个实现将在WebClient.DownloadString块时占用一个线程,等待请求完成。第一个版本没有任何阻塞的线程 - 它依赖于在请求完成时触发的延续。

此外,请考虑Logger.Error来电。在异步版本中,它仍将在原始调用代码的上下文中执行。因此,如果它位于Windows窗体UI中,您仍将位于UI线程上,并且您可以访问UI元素等。在第二个版本中,您将在线程池线程中执行,并且需要编组回UI线程以更新UI。

请注意,您的async void方法几乎肯定不应async void。为了遵守事件处理程序签名,您应该只返回async方法返回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;
        }
    }
}

答案 1 :(得分:6)

对于服务器应用程序,async是关于最小化您获得的被阻止线程的数量:提高线程池的效率,并允许您的程序扩展到更多用户。

对于您不太可能需要关心线程计数的客户端应用程序,async提供了一种相对简单的方法,可以在执行I / O时保持UI运行流畅。

它与引擎盖下的Task.Run大不相同。

答案 2 :(得分:1)

如果您的调用线程有一些有意义的事情要做,比如保持UI响应,您将只能从异步处理中受益。如果你的调用线程只启动一个任务而只是等到任务结束,那么你的进程将运行得更慢。

你的第二个实现启动了一个任务,但你等待它完成而不做任何其他事情。这样你就不会受益。

您没有描述您的环境,但是如果您的UI必须保持响应,那么方法1的实现是可以的,除了您的Start()未声明为异步且不等待:

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

您可以从事件处理程序中调用它,如下所示:

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

注意:ParseHtml之前有await。在上一个解析完成后,将解析下一个html。但是因为解析是异步的,调用线程(UI线程?)将能够做其他事情,比如响应用户输入。

但是,如果您的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
}
  • 如果您调用一个返回任务的函数,您可以在任务运行时继续执行其他操作,或等待任务完成并使用任务的结果。
  • 如果您等待,您的代码会停止,但您的呼叫者会继续处理,直到他们等待您的任务完成为止
  • 如果你的程序是异步的,你只能等待你的程序。
  • 异步函数只能由其他异步函数调用,或者通过调用Task.Run(()=&gt; ...)或者首选:Task.Factory.StartNew(()=&gt; ...)< / LI>
  • 异步函数返回任务
  • 而不是void
  • 而不是TResult异步函数返回任务<TResult&gt;
  • 唯一的例外是事件处理程序:声明它为异步并返回void。
  • 如果您需要完成任务,只需等待任务。
  • await的回归是TResult。