注意:为了本例子的简单起见,我假装我们正在分析当前程序集中的类型,尽管在我的情况下比这更复杂。
我认为实现这个的基本模式如下:
var types = myAssembly.GetTypes();
var tasks = types.ToDictionary( key => key, value => AnalyzeType(value) );
//AnalyzeType() is an async method that returns Task<string>.
现在我们有一个热门任务的字典,可能在创建字典时已经完成,也可能没有完成,因为我没有等待任何事情。
现在要获取结果。我该怎么做?
等待
理论上,我只需要等待每个任务;等待操作的结果是值本身。但这并不会转换任何东西。
var results = tasks.ToDictionary( k => k.key, async v => await v.Value );
Console.WriteLine(results.GetType().FullName);
输出:
System.Collections.Generic.Dictionary'2[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Threading.Tasks.Task'1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
我感到困惑…… 我以为在Task前面加上await
,C#会将其转换为结果。但我仍然得到了一个任务字典。
GetResult()
另一种方法是使用这个:
var results = tasks.ToDictionary( key => key, value => value.GetAwaiter().GetResult() );
Console.WriteLine(results.GetType().FullName);
输出:
System.Collections.Generic.Dictionary'2[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
因此,这似乎可以得到我想要的结果,但是我必须删除await
关键字,现在编译器会发出警告,表示该方法将同步执行。
我可以添加
await Task.WhenAll(tasks.Select( kvp => kvp.Value));
我希望在所有任务完成运行之前不要放弃控制权(因为这可能需要一段时间),所以我的整体解决方案是:
await Task.WhenAll(tasks.Select( kvp => kvp.Value));
var results = tasks.ToDictionary( key => key, value => value.GetAwaiter().GetResult() );
Console.WriteLine(results.GetType().FullName);
我猜这个方法能够运行。但是似乎不是正确的方法;我对调用GetAwaiter().GetResult()
持怀疑态度,并且如果不需要的话,我宁愿不进行额外的WhenAll()
步骤,而实际上也不应该进行,因为我正在单独获取每个任务的等待者和结果。
什么是正确的方法?为什么我的第一个示例中await
关键字没有起作用?我需要使用GetResult()
吗?如果需要,是否包括await Task.WhenAll()
是一个好主意,还是更好地仅依赖于稍后发生的GetAwaiter()
调用?
点击此处查看 Fiddle,如果您想使用它。
编辑(答案):
感谢 Shaun 提供正确答案。如果有人想要将其放入他们的代码库中,这里是一个通用的扩展方法。
public static async Task<Dictionary<TKey, TResult>> ToResults<TKey,TResult>(this IEnumerable<KeyValuePair<TKey, Task<TResult>>> input)
{
var pairs = await Task.WhenAll
(
input.Select
(
async pair => new { Key = pair.Key, Value = await pair.Value }
)
);
return pairs.ToDictionary(pair => pair.Key, pair => pair.Value);
}
WhenAll
之后会出现这样奇怪的value => value.GetAwaiter().GetResult()
?普通的value.Result
就可以了... - Alexei Levenkovvalue.GetAwaiter().GetResult()
的方式更好,它不会像await
那样抛出AggregateException
, 而是抛出第一个异常。它更接近await
的行为,但仍然是阻塞的,因此并不是真正需要的。 - the berserker