【问题标题】:async / await and Task.Run -- How to know when everything is doneasync / await 和 Task.Run -- 如何知道一切何时完成
【发布时间】:2013-01-11 15:56:37
【问题描述】:

我的实际程序比这更复杂,但我试图简化一些事情。

假设我正在读取一个包含 URL 列表的文件。我想从每个 URL 下载 HTML 并处理它。处理可能有点复杂,所以我希望它在单独的线程上完成。

基本问题是如何判断所有处理何时完成。例如,如果用户试图在处理所有 URL 之前关闭程序,我想给他一条消息而不退出程序。或者,我想在处理完所有 URL 后立即终止程序(可能使用 MsgBox("Done") 消息)。

我希望我的代码如下所示(假设我有一个外部循环读取 URL 并调用此例程)...

List<Task> TaskList = new List<Task>();

async void ProcessSingleUrl(string url) {  
var web = new HttpClient();  
    var WebPageContents = await web.GetStringAsync(url);  
    Task t = Task.Run(() => ProcessWebPage(WebPageContents);  
    TaskList.Add(t);
}

上面的代码应该运行得非常快(异步方法立即运行得很好)并且几乎立即返回给调用者。

但那时,我很可能在 TaskList 中没有任何条目,因为在 GetStringAsync 完成之前没有定义任务,并且到那时可能没有(或者可能只有少数)完成。所以

Task.WaitAll(TaskList.ToArray());

没有按我需要的方式工作。

如果绝对有必要,我可以先读入所有 URL,然后知道有多少任务,但我希望有一个更优雅的解决方案。

我想我可以在等待之前增加一个计数器,但这感觉有点笨拙。

我认为我的结构不正确,但我不确定如何重新组织。

注意:我不喜欢 Task.Run。好的 ol' QueueWorkItem 是一种可能性,但我认为它也有同样的问题。

【问题讨论】:

    标签: asynchronous task async-await


    【解决方案1】:

    我认为我的结构不正确,但我不确定如何重新组织。

    我认为这是真的。这是一个可能的解决方案:将整个计算作为Task 存储在您的列表中,而不仅仅是第二部分:

    async Task ProcessSingleUrlInner(string url) {  
        var web = new HttpClient();  
        var WebPageContents = await web.GetStringAsync(url);  
        Task t = Task.Run(() => ProcessWebPage(WebPageContents);  
        await t;
    }
    
    void ProcessSingleUrl(string url) {
    var t = ProcessSingleUrlInner(url);
    TaskList.Add(t);
    }
    

    等待此列表中的所有任务将保证一切都已完成。您可能需要根据您的确切需求调整这个想法。

    【讨论】:

    • 在我看来这行不通。首先,您的意思是“return t;”吗?而不是“等待t;”? (一个“等待”之后什么都没有做,对吧?)如果是这样,我们不是回到了我们开始的地方吗?还是我错过了什么(再次!)?
    • @LarrySmith 如果你遗漏了尾随的 await,ProcessSingleUrlInner 返回的任务将在 before ProcessWebPage 完成之前完成。等待它“包含”ProcessWebPage 到函数中。它确保ProcessSingleUrlInner 在ProcessWebPage 完成之前不会完成。这就是该解决方案的关键:您将获得一项只有在 所有事情 完成后才能完成的任务。这就是你想要的,对吧?
    【解决方案2】:

    我假设您以IEnumerable&lt;string&gt; 或类似的形式获取网址列表。

    您可以使用LINQ将每个url转换成Task,然后await全部完成:

    async Task ProcessUrls(IEnumerable<string> urls)
    {
      var tasks = urls.Select(async url =>
      {
        var web = new HttpClient();  
        var WebPageContents = await web.GetStringAsync(url);  
        await Task.Run(() => ProcessWebPage(WebPageContents);
      });
      await Task.WhenAll(tasks);
    }
    

    请注意,如果您使用此解决方案并且有多个不同的 url 有错误,那么Task.WhenAll 将只报告其中一个错误。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-08-07
      • 2022-01-07
      • 1970-01-01
      相关资源
      最近更新 更多