16

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

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

4

3 回答 3

29

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

这听起来像是一个非常奇怪的理由。您正在尝试从根本上“有点异步”执行 - 那么为什么不说清楚呢?

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

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

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

请注意,您的async void方法几乎肯定不应该async void. 为了遵守事件处理程序签名,您应该只async返回方法。void在所有其他情况下,返回Task- 这样调用者可以看到您的任务何时完成,处理异常等。

另请注意,您不需要HttpClient用于异步 - 您可以WebClient.DownloadStringTaskAsync改为使用,因此您的最终方法可能变为:

private async Task<string> DownloadHtmlAsync(string query)
{
    using (var client = new WebClient())
    {
        try
        {
            return await client.DownloadStringTaskAsync(query);
        }
        catch (Exception ex)
        {
            Logger.Error(msg, ex);
            return null;
        }
    }
}
于 2012-11-23T22:59:06.060 回答
6

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

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

Task.Run它与引擎盖下面有很大不同。

于 2012-11-24T17:27:25.693 回答
1

只有当你的调用线程有一些有意义的事情要做时,你才会从异步处理中受益,比如保持 UI 响应。如果你的调用线程只启动一个任务并且什么都不做,只是等到任务完成,你的进程会运行得更慢。

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

你没有描述你的环境,但是如果你有一个必须保持响应的 UI,那么方法 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 前面有 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( () => ...) 或者如果愿意:Task.Factory.StartNew( () => ...)
  • 异步函数返回 Task 而不是 void
  • 而不是 TResult 一个异步函数返回 Task <TResult>
  • 唯一的例外是事件处理程序:将其声明为异步并返回 void。
  • 如果您需要完成任务,只需等待任务即可。
  • await 的返回是 TResult。
于 2015-08-10T11:53:06.590 回答