如何运行多个任务,处理异常并仍然返回结果

时间:2014-11-20 14:18:43

标签: c# task-parallel-library

我正在更新我的并发技能。我的问题似乎相当普遍:从多个Uris读取,解析并使用结果等等。我有Concurrency in C# Cookbook。有一些使用GetStringAsync的例子,例如

static async Task<string> DownloadAllAsync(IEnumerable<string> urls)
{
    var httpClient = new HttpClient();

    var downloads = urls.Select(url => httpClient.GetStringAsync(url));

    Task<string>[] downloadTasks = downloads.ToArray();

    string[] htmlPages = await Task.WhenAll(downloadTasks);

    return string.Concat(htmlPages);
}

我需要的是运行多个异步任务的异步模式,捕获全部或部分成功。

  1. Url 1成功
  2. Url 2成功
  3. Url 3失败(超时,糟糕的Uri格式,401等)
  4. Url 4成功
  5. ... 20多个成功的混合
  6. 等待DownloadAllAsync任务将抛出单个聚合异常,如果有任何失败,则丢弃累积的结果。从我的有限研究来看,WhenAll或WaitAll表现相同。我想捕获异常,记录失败,但继续执行剩余的任务,即使它们都失败了。 我可以一个一个地处理它们,但是这不会破坏允许TPL管理整个过程的目的吗?是否有一个链接到一个模式,以纯TPL方式实现这一点?也许我使用了错误的工具?

2 个答案:

答案 0 :(得分:19)

  

我想捕获异常,记录失败,但继续执行剩余的任务,即使它们都失败了。

在这种情况下,最干净的解决方案是更改代码为每个元素执行的操作。即,这个当前代码:

var downloads = urls.Select(url => httpClient.GetStringAsync(url));

对每个网址说&#34;下载一个字符串&#34;。你想要它说的是&#34;对于每个网址,下载一个字符串,然后记录并忽略任何错误&#34;:

static async Task<string> DownloadAllAsync(IEnumerable<string> urls)
{
  var httpClient = new HttpClient();
  var downloads = urls.Select(url => TryDownloadAsync(httpClient, url));
  Task<string>[] downloadTasks = downloads.ToArray();
  string[] htmlPages = await Task.WhenAll(downloadTasks);
  return string.Concat(htmlPages);
}

static async Task<string> TryDownloadAsync(HttpClient client, string url)
{
  try
  {
    return await client.GetStringAsync(url);
  }
  catch (Exception ex)
  {
    Log(ex);
    return string.Empty; // or whatever you prefer
  }
}

答案 1 :(得分:4)

您可以为所有任务附加延续并等待它们,而不是直接等待任务。

static async Task<string> DownloadAllAsync(IEnumerable<string> urls)
{
    var httpClient = new HttpClient();

    IEnumerable<Task<Task<string>>> downloads = urls.Select(url => httpClient.GetStringAsync(url).ContinueWith(p=> p, TaskContinuationOptions.ExecuteSynchronously));

    Task<Task<string>>[] downloadTasks = downloads.ToArray();

    Task<string>[] compleTasks =  await Task.WhenAll(downloadTasks);

    foreach (var task in compleTasks)
    {
        if (task.IsFaulted)//Or task.IsCanceled
        {
            //Handle it
        }
    }

    var htmlPages = compleTasks.Where(x => x.Status == TaskStatus.RanToCompletion)
        .Select(x => x.Result);

    return string.Concat(htmlPages);
}

只要一个任务失败,它就不会停止,而是等待所有任务完成。然后分别处理成功和失败。