如何运行多个任务、处理异常并仍返回结果
本文关键字:异常 处理 结果 返回 任务 何运行 运行 | 更新日期: 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 成功
- 网址 2 成功
- URL 3 失败(超时、错误的 Uri 格式、401 等)
- 网址 4 成功
- 。还有 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);
}
这不会在一个任务失败时立即停止,而是等待所有任务完成。然后分别处理成功和失败。