将任务字典转换为结果字典

时间:2018-03-30 22:39:42

标签: c# .net asynchronous task

我有一个程序必须处理许多对象并产生分析。每个分析都会生成一个字符串,并连接字符串以创建报告。报告需要按特定顺序排列结果,但我想异步分析每个项目,所以我通过将所有内容放入字典来管理它,然后我可以在准备最终输出之前对其进行排序。

注意:为了这个例子,我假装我们正在分析当前程序集中的类型,尽管在我看来它比这更复杂。

这样做的基本模式(我认为)就像这样:

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

3 个答案:

答案 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)

这对我有帮助:

https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-return-a-value-from-a-task

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;