I have a program that has to process a number of objects and produce an analysis. Each analysis results in a string, and the strings are concatenated to create a report. The report needs the results in a certain order, but I want to analyze each item asynchronously, so I manage this by putting everything into a dictionary, which I can then sort before preparing the final output.
Note: For the sake of this example I will pretend that we are analyzing types from the current assembly, although in my case it's more complicated than that.
The basic pattern (I thought) for doing this would be like this:
var types = myAssembly.GetTypes();
var tasks = types.ToDictionary( key => key, value => AnalyzeType(value) );
//AnalyzeType() is an async method that returns Task<string>.
Now we have a dictionary of hot tasks, which may or may not be finished by the time the dictionary is created, because I didn't await anything.
Now to get the results. How do I do it?
Await
In theory all I have to do is await each task; the result of the await operation is the value itself. But this doesn't convert anything.
var results = tasks.ToDictionary( k => k.key, async v => await v.Value );
Console.WriteLine(results.GetType().FullName);
Output:
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]]
I am baffled... I thought by putting await
in front of the Task, c# would convert it to the result. But I still have a dictionary of tasks.
GetResult()
Another approach would be to use this:
var results = tasks.ToDictionary( key => key, value => value.GetAwaiter().GetResult() );
Console.WriteLine(results.GetType().FullName);
Output:
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]]
So this appears to get me what I wanted, but I had to remove the await
keyword, and now I get a warning from the compiler that the method will execute synchronously.
I could add
await Task.WhenAll(tasks.Select( kvp => kvp.Value));
...to yield control until all the tasks have finished running (since it might take a while), so then my overall solution would be:
await Task.WhenAll(tasks.Select( kvp => kvp.Value));
var results = tasks.ToDictionary( key => key, value => value.GetAwaiter().GetResult() );
Console.WriteLine(results.GetType().FullName);
Which I guess works. But seems like this isn't the right way; I am suspicious of calling GetAwaiter().GetResult()
, and I'd rather not do the extra WhenAll()
step if it isn't needed, and it shouldn't be, since I'm getting the awaiters and results for each and every task individually.
What is the right way to do this? Why didn't the await
keyword work in my first example? Do I need to GetResult()
? And if I do, is it a good idea to include await Task.WhenAll()
, or is it better to simply rely on the GetAwaiter()
call (which occurs later anyway)?
Click here for a Fiddle if you'd like to work with it.
Thanks Shaun for the correct answer. Here is a generalizable extension method if anyone wants something they can just drop into their code base.
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);
}
Why didn't the await keyword work in my first example?
The await
keyword unwraps the Task<T>
within the context of an async
method, operates on the underlying result of type <T>
, and wraps the async
method's return value back in a Task
. That is why every async
method/function returns one of void
, Task
, or Task<T>
(note that void
is only appropriate for events). An async
method does not return an unwrapped value; we never see a method signature like public async int SomeMethod()
, because returning int
would not compile in an async
method. It would need to return a Task<int>
instead.
What is the right way to do this?
Here is one approach (with a Fiddle) to converting a dictionary with values of type Task<T>
to a dictionary with values of type <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
}
}
The solution is to get rid of the ToDictionary call and build the dictionary yourself:
var result = new Dictionary<Type, string>();
foreach (var kvp in tasks)
{
result[kvp.Key] = await kvp.Value;
}
The reason why ToDictionary doesn't work is that you want the value pulled out of the task not another task. Creating an async lambda just creates another task that you then have to await.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With