我有一个程序必须处理许多对象并产生分析。每个分析都会生成一个字符串,并连接字符串以创建报告。报告需要按特定顺序排列结果,但我想异步分析每个项目,所以我通过将所有内容放入字典来管理它,然后我可以在准备最终输出之前对其进行排序。
注意:为了这个例子,我假装我们正在分析当前程序集中的类型,尽管在我看来它比这更复杂。
这样做的基本模式(我认为)就像这样:
var types = myAssembly.GetTypes();
var tasks = types.ToDictionary( key => key, value => AnalyzeType(value) );
//AnalyzeType() is an async method that returns Task<string>.
现在我们有一个热门任务的字典,在字典创建时可能会或者可能不会完成,因为我没有等待任何事情。
现在得到结果。我该怎么做?
等待
理论上,我所要做的就是等待每项任务; await操作的结果是值本身。但这并没有转换任何东西。
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]]
我很困惑......我想通过将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()
步骤,也不应该这样做,因为我&#39 ;单独获取每个任务的等待者和结果。
这样做的正确方法是什么?为什么我的第一个例子中没有await
关键字?我需要GetResult()
吗?如果我这样做,是否包含await Task.WhenAll()
是个好主意,还是仅仅依靠GetAwaiter()
调用(以后会发生)更好?
Click here for a 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);
}
答案 0 :(得分:7)
为什么await关键字在我的第一个例子中不起作用?
await
关键字在Task<T>
方法的上下文中展开async
,对<T>
类型的基础结果进行操作,并包装async
方法的返回值返回Task
。这就是每个async
方法/函数returns one of void
, Task
, or Task<T>
的原因(请注意void
仅适用于事件)。 async
方法不返回未包装的值;我们从未看到类似public async int SomeMethod()
的方法签名,因为返回int
不会在async
方法中编译。它需要返回Task<int>
。
这样做的正确方法是什么?
以下是将值为Task<T>
的字典转换为值为<T>
的字典的一种方法(with a Fiddle):
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public async static void Main()
{
// create a dictionary of 10 tasks
var tasks = Enumerable.Range(0, 10)
.ToDictionary(i => i, i => Task.FromResult(i * i));
// await all their results
// mapping to a collection of KeyValuePairs
var pairs = await Task.WhenAll(
tasks.Select(
async pair =>
new KeyValuePair<int, int>(pair.Key, await pair.Value)));
var dictionary = pairs.ToDictionary(p => p.Key);
System.Console.WriteLine(dictionary[2].Value); // 4
}
}
答案 1 :(得分:0)
解决方案是摆脱ToDictionary调用并自己构建字典:
var result = new Dictionary<Type, string>();
foreach (var kvp in tasks)
{
result[kvp.Key] = await kvp.Value;
}
ToDictionary不起作用的原因是您希望从任务中取出的值不是另一项任务。创建异步lambda只会创建另一个您必须等待的任务。
答案 2 :(得分:0)
这对我有帮助:
Dictionary<string, bool> rtnDict = new Dictionary<string, bool>();
var tasks = _teachService.ReadTagStateAsync(module, moduleCode, sourceName);
foreach(var aDict in tasks.Result)
{
rtnDict.Add(aDict.Key, aDict.Value);
}
return rtnDict;