patterncsharpMinor
Handle concurrent request by waiting the result of an already running operation
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.
Do I have a bugs? Is it thread safe? How can I improve my code? I'm using .net framework 4.5.2
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:
To prove that it is properly working, I run a 100 concurrent threads test with random access to the
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
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 sameCredentialinstance, after the call toGetOrAdd(),addedistruefor only the first invoking thread. OK.
- All other threads get the existing
Lazyinstance after callingGetOrAdd()andaddedisfalse. OK
GetDataInternal()is called on one of the first threads that callGetOrAdd(), most of the time the adding thread, but sometimes on another. OK
- After the call to
GetDataInternal(), all requesting threads get the sameDatainstance out oflazy.Valueone by one, as returned byGetDataInternal(). OK
- The first thread that gets the
Datainstance callsTryRemove()and succeeds. OK.
- After the
Lazyinstance is removed, other threads continue to get the oldDatainstance because they have the removedLazyinstance. 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.