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

Joh*_* Wu 4 .net c# asynchronous task

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

注意:在本例中,我将假装我们正在分析当前程序集中的类型,尽管在我看来,它比这要复杂得多。

(我认为)执行此操作的基本模式如下:

var types = myAssembly.GetTypes();
var tasks = types.ToDictionary( key => key, value => AnalyzeType(value) );
//AnalyzeType() is an async method that returns Task<string>.
Run Code Online (Sandbox Code Playgroud)

现在我们有了一个热门任务字典,由于我什么都没等,所以在创建字典时可能会完成也可能不会完成。

现在获取结果。我该怎么做?

等待

从理论上讲,我要做的就是等待每个任务。等待操作的结果是值本身。但这并不能转换任何东西。

var results = tasks.ToDictionary( k => k.key, async v => await v.Value );
Console.WriteLine(results.GetType().FullName);
Run Code Online (Sandbox Code Playgroud)

输出:

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]]

我感到困惑……我想通过将awaitTask 放在前面,C#会将其转换为结果。但是我还有任务字典。

GetResult()

另一种方法是使用此方法:

var results = tasks.ToDictionary( key => key, value => value.GetAwaiter().GetResult() );
Console.WriteLine(results.GetType().FullName);
Run Code Online (Sandbox Code Playgroud)

输出:

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));
Run Code Online (Sandbox Code Playgroud)

...直到所有任务都完成运行为止(因为可能要花一些时间),以便进行控制,所以我的总体解决方案是:

await Task.WhenAll(tasks.Select( kvp => kvp.Value));
var results = tasks.ToDictionary( key => key, value => value.GetAwaiter().GetResult() );
Console.WriteLine(results.GetType().FullName);
Run Code Online (Sandbox Code Playgroud)

我猜这可行。但是似乎这不是正确的方法。我对致电表示怀疑,如果不需要,GetAwaiter().GetResult()也不想做多余的WhenAll()步骤,也不应该这样做,因为我会分别获得每个任务的等待者和结果。

什么是正确的方法?为什么await我的第一个示例中的关键字不起作用?我需要GetResult()吗?如果可以的话,包含还是一个好主意await Task.WhenAll(),或者仅仅依靠该GetAwaiter()调用(无论如何稍后会发生)会更好吗?

如果您想使用小提琴,请单击此处

编辑(答案):

感谢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);
}
Run Code Online (Sandbox Code Playgroud)

Sha*_*tin 7

为什么第一个示例中的await关键字不起作用?

await关键字解包Task<T>的的范围内async的方法,上式的基本结果进行操作<T>,并封装async在一个方法的返回值返回Task。这就是为什么每一个async方法/函数返回的一个voidTask或者Task<T>(注意,void是最合适不过的事件)。的async方法不返回展开的值; 我们从未见过像这样的方法签名public async int SomeMethod(),因为返回int不会在async方法中编译。它需要返回一个Task<int>代替。

什么是正确的方法?

这是一种使用Fiddle的方法将具有类型值的字典转换为具有类型值的Task<T>字典<T>

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
    }
}
Run Code Online (Sandbox Code Playgroud)