Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

call valuefactory in concurrent dictionary in async way

I am new to C#'s ConcurrentDictionary class, and I wonder how I can use a valueFactory in the GetOrAdd method in an async way.

public class Class1
{
    public int X = 10;

    public Class1(int x)
    {
        X = x;
        Debug.WriteLine("Class1 Created");
    }
}

Testing code logic in a WinForm button:

private async void button1_Click(object sender, EventArgs e)
{
    var asyncDict = new ConcurrentDictionary<int, Task<Class1>>();

    Func<int, Task<Class1>> valueFactoryAsync = async (k) => new Class1(await Task.Run(async () =>
    {
        await Task.Delay(2000);
        Debug.WriteLine("Async Factory Called");
        return 5;
    }));

    var temp = await(asyncDict.GetOrAdd(1, valueFactoryAsync)).ConfigureAwait(false);
    Debug.WriteLine(temp.X);
    temp.X = 20;
    var temp2 = await (asyncDict.GetOrAdd(1, valueFactoryAsync)).ConfigureAwait(false);
    Debug.WriteLine(temp2.X);
}

When GetOrAdd method is called in the second time, it returns a Task from the concurrent dictionary. Then I called await on the Task, var temp2 = await (asyncDict.GetOrAdd(1,valueFactoryAsync)).ConfigureAwait(false); it seems like the underlying Task(or async method) was not called again and return a new Class1 object.Was it just returned the Class1 object generated in the first call? Could you advise what is happening under the hood?

like image 823
Sean Avatar asked Nov 10 '22 06:11

Sean


1 Answers

Yes, you get the same instance when you call asyncDict.GetOrAdd(1,valueFactoryAsync) the second time. Why does that surprise you? You have provided the same key as in the first time.

what is happening under the hood?

var temp = await(asyncDict.GetOrAdd(1, valueFactoryAsync)).ConfigureAwait(false);

When this line is called, there is no item in the dictionary for key=1, so valueFactoryAsync is called, returns a Task with Status=Created, and this Task is added to the dictionary. That's it for the Add part. Now begins the Get part, and you get back that new ready-to-run Task. You actually awaits this task (var temp = await (...)), so the Task begins and your next lines of code won't execute until this Task is done. I hope the rest is quite straightforward from here - the next time you call await (asyncDict.GetOrAdd(1, valueFactoryAsync)) - for temp2 - there is already an item in the dictionary for this key - a completed Task, and that's the Task you get. Note, that even if you weren't await the first call, the dictionary would still had been populated with Created Task, and on the second call you still get the same Task (and hence the same Class1 instance if you await / .Result it)

like image 120
shay__ Avatar answered Nov 14 '22 22:11

shay__