我正在构建一个需要执行许多并发任务的应用程序 . 这些任务包含对不同异步方法的多次调用(大多数情况下,它使用HttpClient查询一些REST API),中间有一些处理,我真的不想在任务中捕获异常 . 相反,我宁愿在使用WhenAny / WhenAll方法等待它们时这样做 . 当获得异常时,我还需要捕获该任务的一些源数据以供将来分析(例如,将其写入日志中) .
有一个Task.AsyncState属性,它似乎是这个数据的完美容器 . 所以,我在Task的创建上传递一个TaskState对象,如下所示:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ConsoleApp5
{
class Program
{
static void Main(string[] args)
{
new Program().RunTasksAsync();
Console.ReadLine();
}
class TaskState
{
public int MyValue { get; }
public TaskState(int myValue)
{
MyValue = myValue;
}
}
private async Task<int> AsyncMethod1(int value)
{
await Task.Delay(500);
return value + 1;
}
private async Task<int> AsyncMethod2(int value)
{
await Task.Delay(500);
if (value % 3 == 0)
throw new Exception("Expected exception");
else
return value * 2;
}
private async Task DoJobAsync(object state)
{
var taskState = state as TaskState;
var i1 = await AsyncMethod1(taskState.MyValue);
var i2 = await AsyncMethod2(i1);
Console.WriteLine($"The result is {i2}");
}
private async void RunTasksAsync()
{
const int tasksCount = 10;
var tasks = new List<Task>(tasksCount);
var taskFactory = new TaskFactory();
for (var i = 0; i < tasksCount; i++)
{
var state = new TaskState(i);
var task = taskFactory.StartNew(async state => await DoJobAsync(state), state).Unwrap();
tasks.Add(task);
}
while (tasks.Count > 0) {
var task = await Task.WhenAny(tasks);
tasks.Remove(task);
var state = task.AsyncState as TaskState;
if (task.Status == TaskStatus.Faulted)
Console.WriteLine($"Caught an exception while executing task, task data: {state?.MyValue}");
}
Console.WriteLine("All tasks finished");
}
}
}
上面代码的问题是Unwrap()创建一个Task的代理,它不是AsyncState值,它总是为null,所以当遇到异常时,我无法获得原始任务的状态 . 如果我删除委托中的Unwrap()和async / await修饰符,WaitAny()方法在任务实际完成之前完成 .
如果我使用 Task.Run(() => DoJobAsync(state))
而不是TaskFactory.StartNew(),任务按正确顺序完成,但这样我就可以't set the AsyncState on tasks'创建 .
当然,我可以使用 Dictionary<Task, TaskState>
来保存任务参数以防万一,但对我来说似乎有点脏,而且,与AsyncState属性的使用相比,在这个集合中搜索匹配需要花费一些时间 . 大量的并发任务 .
在这种情况下,有人会建议更优雅的解决方案吗?还有另一种方法可以在异常中获取Task的状态吗?
2 回答
一个优雅的解决方案是:
通过这种方式传递数据,执行顺序正确 .
为了记录的目的,包含有关操作正在进行的操作的信息的适当位置,当它失败时,就是你抛出的异常 . 不要强制调用者不仅要跟踪任务,还要跟踪有关该任务的大量信息,以便他们能够正确处理错误 .
如果您的实际代码中抛出的异常没有被显式抛出,而是由您无法控制的代码抛出,那么您可能需要捕获这些异常并将它们包装在您自己的新异常中(意味着它将会需要接受内部异常并将其传递给基础构造函数) .
还有一些需要注意的事项,不要使用
StartNew
或Run
来运行已经异步的操作 . 只需运行该方法 . 您不需要启动已经异步操作的新线程池线程(除非它开始时编写不正确) . 如果要在任务失败时运行某些代码,请使用try catch,而不是尝试执行您正在执行的操作 . 它增加了很多复杂性,并且有更多的错误空间 .甚至还需要's worth noting that, due to how I refactored the error handling code, the initial input data is still in scope, so including it in the exception isn',但我可能会将错误处理代码放在调用堆栈的上方,或者要打印的信息不仅仅是提供给函数的输入 . 但是如果这些都不成立,你可以只使用
catch
块中的输入数据,因为它仍在范围内 .