2

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

假设我正在读取一个包含 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。Good ol' QueueWorkItem 是一种可能性,但我认为它也有同样的问题。

4

2 回答 2

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);
}

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

于 2013-01-11T16:13:11.150 回答
0

我假设您正在获取IEnumerable<string>诸如此类的 url 列表。

您可以使用 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只会报告其中一个错误。

于 2013-01-11T18:21:16.270 回答