HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Handle concurrent request by waiting the result of an already running operation

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
resultthehandlewaitingrequestoperationalreadyrunningconcurrent

Problem

I need to handle concurrent request by waiting the result of an already running operation.

Requests for data may come in simultaneously with same/different credentials.

For each unique set of credentials there can be at most one GetDataInternal call in progress, with the result from that one call returned to all queued waiters when it is ready.

After this the results of the previous call are invalidated, and a new GetDataInternal call will be allowed with the same set of credentials.

Parallel calls to GetDataInternal with different credentials are allowed.

private readonly ConcurrentDictionary> Cache
= new ConcurrentDictionary>();

public Data GetData(Credential credential)
{
    // This instance will be thrown away if a cached
    // value with our "credential" key already exists.
    Lazy newLazy = new Lazy(
        () => GetDataInternal(credential),
        LazyThreadSafetyMode.ExecutionAndPublication
    );

    Lazy lazy = Cache.GetOrAdd(credential, newLazy);
    bool added = ReferenceEquals(newLazy, lazy); // If true, we won the race.
    Data data;

    try
    {
       // Wait for the GetDataInternal call to complete.
       data = lazy.Value;
    }
    finally
    {
        // Only the thread which created the cache value
        // is allowed to remove it, to prevent races.
        if (added) {
            Cache.TryRemove(credential, out lazy);
        }
    }

    return data;
}


Do I have a bugs? Is it thread safe? How can I improve my code? I'm using .net framework 4.5.2

Solution

Reading through the code, I can say that it is well written, concurrent access in mind, and at the first sight, it can be said that it has no flaws.

I especially liked these lines:

Lazy lazy = Cache.GetOrAdd(credential, newLazy);
bool added = ReferenceEquals(newLazy, lazy); // If true, we won the race.


To prove that it is properly working, I run a 100 concurrent threads test with random access to the GetData() operation with the same credential instance. This test revealed that,

  • The access is synchronized to the GetDataInternal() method. OK.



  • For the threads that call GetData() at the same time passing the same Credential instance, after the call to GetOrAdd(), added is true for only the first invoking thread. OK.



  • All other threads get the existing Lazy instance after calling GetOrAdd() and added is false. OK



  • GetDataInternal() is called on one of the first threads that call GetOrAdd(), most of the time the adding thread, but sometimes on another. OK



  • After the call to GetDataInternal(), all requesting threads get the same Data instance out of lazy.Value one by one, as returned by GetDataInternal(). OK



  • The first thread that gets the Data instance calls TryRemove() and succeeds. OK.



  • After the Lazy instance is removed, other threads continue to get the old Data instance because they have the removed Lazy instance. OK



No concurrency issues, nothing.

The code is working properly.

```
ORIGINAL ORDER THREAD ID METHOD EVENT NAME RESULT TOTAL MS
2 T:8 M:GetData E:end_call_GetOrAdd()->added R:True 4,82
1 T:9 M:GetData E:end_call_GetOrAdd()->added R:False 5,09
12 T:21 M:GetData E:end_call_GetOrAdd()->added R:False 5,3
9 T:30 M:GetData E:end_call_GetOrAdd()->added R:False 5,32
13 T:3 M:GetData E:end_call_GetOrAdd()->added R:False 5,33
49 T:31 M:GetData E:end_call_GetOrAdd()->added R:False 5,56
20 T:22 M:GetData E:end_call_GetOrAdd()->added R:False 6,07
11 T:7 M:GetData E:end_call_GetOrAdd()->added R:False 6,19
8 T:18 M:GetData E:end_call_GetOrAdd()->added R:False 6,41
3 T:23 M:GetData E:end_call_GetOrAdd()->added R:False 7,01
4 T:10 M:GetData E:end_call_GetOrAdd()->added R:False 7,29
5 T:6 M:GetData E:end_call_GetOrAdd()->added R:False 7,29
15 T:28 M:GetData E:end_call_GetOrAdd()->added R:False 8,39
70 T:24 M:GetData E:end_call_GetOrAdd()->added R:False 9,26
10 T:17 M:GetData E:end_call_GetOrAdd()->added R:False 9,29
45 T:13 M:GetData E:end_call_GetOrAdd()->added R:False 9,31
18 T:20 M:GetData E:end_call_GetOrAdd()->added R:False 9,72
7 T:25 M:GetData E:end_call_GetOrAdd()->added R:False 10,38
40 T:26 M:GetData E:end_call_GetOrAdd()->added R:False 10,4
22 T:14 M:GetData E:end_call_GetOrAdd()->added R:False 10,6
65 T:15 M:GetData E:end_call_GetOrAdd()->added R:False 10,67
52 T:12 M:GetData E:end_call_GetOrAdd()->added R:False 11,04
47 T:5 M:GetData E:end_call_GetOrAdd()->added R:False 11,5
42 T:19 M:GetData E:end_call_GetOrAdd()->added R:False 11,81
51 T:4 M:GetData E:end_call_GetOrAdd()->added R:False 12,34
33 T:11 M:GetData E:end_call_GetOrAdd()->added R:False 12,91
27 T:29 M:GetData E:end_call_GetOrAdd()->added R:False 13,62
36 T:27 M:GetData E:end_call_GetOrAdd()->added R:False 14,01
6 T:16 M:GetData E:end_call_GetOrAdd()->added R:False 14,88
14 T:9 M:GetDataInternal E:Return R:[Data_1] 18,21
17 T:9 M:GetData E:end_get_lazy.Value R:[Data_1] 18,29
16 T:28 M:GetData E:end_get_lazy.Value R:[Data_1] 18,33
19 T:20 M:GetData E:end_get_lazy.Value R:[Data_1] 18,58
21 T:22 M:GetData E:end_get_lazy.Value R:[Data_1] 18,71
23 T:14 M:GetData E:end_get_lazy.Value R:[Data_1] 18,86
24 T:8 M:GetData E:end_get_lazy.Value R:[Data_1] 18,91
25 T:8 M:GetData E:begin_call_TryRemove R:- 18,96
50 T:25 M:GetData E:end_get_lazy.Value R:[Data_1] 19,83
26 T:8 M:GetData E:end_call_TryRemove R:True 19,86
39 T:10 M:GetData E:end_get_lazy.Value R:[Data_1] 20,67
38 T:6 M:GetData E:end_get_lazy.Value R:[Data_1] 21,56
31 T:17 M:GetData E:end_get_lazy.Value R:[Data_1] 21,6
28 T:29 M:GetData E:end_get_lazy.Value R:[Data_1] 22
29 T:32 M:GetData E:end_call_GetOrAdd()->added R:True 22,46
53 T:18 M:GetData E:end_get_lazy.Value R:[Data_1] 22,61
30 T:21 M:GetData E:end_get_lazy.Value R:[Data_1] 24,47
32 T:30 M:GetData E:end_get_lazy.Value R:[Data_1] 24,91
35 T:3 M:GetData E:end_get_lazy.Value R:[Data_1] 25,13
34 T:11 M:GetData E:end_get_lazy.Value R:[Data_1] 25,78
37 T:27 M:GetData E:end_get_lazy.Value R:[Data_1] 27,08
41 T:26 M:GetData E:end_get_lazy.Value R:[Data_1] 30,53
43 T:19 M:GetData E:end_get_lazy.Value R:[Data_1] 31,13
4

Code Snippets

Lazy<Data> lazy = Cache.GetOrAdd(credential, newLazy);
bool added = ReferenceEquals(newLazy, lazy); // If true, we won the race.
ORIGINAL ORDER  THREAD ID   METHOD  EVENT NAME  RESULT  TOTAL MS
2   T:8 M:GetData   E:end_call_GetOrAdd()->added    R:True  4,82
1   T:9 M:GetData   E:end_call_GetOrAdd()->added    R:False 5,09
12  T:21    M:GetData   E:end_call_GetOrAdd()->added    R:False 5,3
9   T:30    M:GetData   E:end_call_GetOrAdd()->added    R:False 5,32
13  T:3 M:GetData   E:end_call_GetOrAdd()->added    R:False 5,33
49  T:31    M:GetData   E:end_call_GetOrAdd()->added    R:False 5,56
20  T:22    M:GetData   E:end_call_GetOrAdd()->added    R:False 6,07
11  T:7 M:GetData   E:end_call_GetOrAdd()->added    R:False 6,19
8   T:18    M:GetData   E:end_call_GetOrAdd()->added    R:False 6,41
3   T:23    M:GetData   E:end_call_GetOrAdd()->added    R:False 7,01
4   T:10    M:GetData   E:end_call_GetOrAdd()->added    R:False 7,29
5   T:6 M:GetData   E:end_call_GetOrAdd()->added    R:False 7,29
15  T:28    M:GetData   E:end_call_GetOrAdd()->added    R:False 8,39
70  T:24    M:GetData   E:end_call_GetOrAdd()->added    R:False 9,26
10  T:17    M:GetData   E:end_call_GetOrAdd()->added    R:False 9,29
45  T:13    M:GetData   E:end_call_GetOrAdd()->added    R:False 9,31
18  T:20    M:GetData   E:end_call_GetOrAdd()->added    R:False 9,72
7   T:25    M:GetData   E:end_call_GetOrAdd()->added    R:False 10,38
40  T:26    M:GetData   E:end_call_GetOrAdd()->added    R:False 10,4
22  T:14    M:GetData   E:end_call_GetOrAdd()->added    R:False 10,6
65  T:15    M:GetData   E:end_call_GetOrAdd()->added    R:False 10,67
52  T:12    M:GetData   E:end_call_GetOrAdd()->added    R:False 11,04
47  T:5 M:GetData   E:end_call_GetOrAdd()->added    R:False 11,5
42  T:19    M:GetData   E:end_call_GetOrAdd()->added    R:False 11,81
51  T:4 M:GetData   E:end_call_GetOrAdd()->added    R:False 12,34
33  T:11    M:GetData   E:end_call_GetOrAdd()->added    R:False 12,91
27  T:29    M:GetData   E:end_call_GetOrAdd()->added    R:False 13,62
36  T:27    M:GetData   E:end_call_GetOrAdd()->added    R:False 14,01
6   T:16    M:GetData   E:end_call_GetOrAdd()->added    R:False 14,88
14  T:9 M:GetDataInternal   E:Return    R:[Data_1]  18,21
17  T:9 M:GetData   E:end_get_lazy.Value    R:[Data_1]  18,29
16  T:28    M:GetData   E:end_get_lazy.Value    R:[Data_1]  18,33
19  T:20    M:GetData   E:end_get_lazy.Value    R:[Data_1]  18,58
21  T:22    M:GetData   E:end_get_lazy.Value    R:[Data_1]  18,71
23  T:14    M:GetData   E:end_get_lazy.Value    R:[Data_1]  18,86
24  T:8 M:GetData   E:end_get_lazy.Value    R:[Data_1]  18,91
25  T:8 M:GetData   E:begin_call_TryRemove  R:- 18,96
50  T:25    M:GetData   E:end_get_lazy.Value    R:[Data_1]  19,83
26  T:8 M:GetData   E:end_call_TryRemove    R:True  19,86
39  T:10    M:GetData   E:end_get_lazy.Value    R:[Data_1]  20,67
38  T:6 M:GetData   E:end_get_lazy.Value    R:[Data_1]  21,56
31  T:17    M:GetData   E:end_get_lazy.Value    R:[Data_1]  21,6
28  T:29    M:GetData   E:end_get_lazy.Value    R:[Data_1]  22
29  T:32    M:GetData   E:end_call_GetOrAdd()->added    R:True  22,46
53  

Context

StackExchange Code Review Q#113827, answer score: 3

Revisions (0)

No revisions yet.