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

本文关键字:异常 处理 结果 返回 任务 何运行 运行 | 更新日期: 2023-09-27 18:31:17

我正在更新我的并发技能集。我的问题似乎相当普遍:从多个 Uri 读取、解析和处理结果等。我在 C# 食谱中有并发性。有一些使用 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. 网址 1 成功
  2. 网址 2 成功
  3. URL 3 失败(超时、错误的 Uri 格式、401 等)
  4. 网址 4 成功
  5. 。还有 20 个成功与否

等待 DownloadAllAsync 任务将引发单个聚合异常(如果有任何失败),从而丢弃累积的结果。根据我有限的研究,WhenAll或WaitAll的行为相同。我想捕获异常,记录失败,但继续执行剩余的任务,即使它们都失败了。我可以一个接一个地处理它们,但这不会违背允许 TPL 管理整个过程的目的吗?是否有指向以纯TPL方式完成此操作的模式的链接?也许我使用了错误的工具?

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

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

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

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

说"对于每个 URL,下载一个字符串"。您希望它说的是"对于每个 url,下载一个字符串,然后记录并忽略任何错误":

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

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

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

这不会在一个任务失败时立即停止,而是等待所有任务完成。然后分别处理成功和失败。