ConcurrentDictionary GetOrAdd async - c#

I want to use something like GetOrAdd with a ConcurrentDictionary as a cache to a webservice. Is there an async version of this dictionary? GetOrAdd will be making a web request using HttpClient, so it would be nice if there was a version of this dictionary where GetOrAdd was async.
To clear up some confusion, the contents of the dictionary will be the response from a call to a webservice.
ConcurrentDictionary<string, Response> _cache
= new ConcurrentDictionary<string, Response>();
var response = _cache.GetOrAdd("id",
(x) => { _httpClient.GetAsync(x).GetAwaiter().GetResponse(); });

GetOrAdd won't become an asynchronous operation because accessing the value of a dictionary isn't a long running operation.
What you can do however is simply store tasks in the dictionary, rather than the materialized result. Anyone needing the results can then await that task.
However, you also need to ensure that the operation is only ever started once, and not multiple times. To ensure that some operation runs only once, and not multiple times, you also need to add in Lazy:
ConcurrentDictionary<string, Lazy<Task<Response>>> _cache = new ConcurrentDictionary<string, Lazy<Task<Response>>>();
var response = await _cache.GetOrAdd("id", url => new Lazy<Task<Response>>(_httpClient.GetAsync(url))).Value;

The GetOrAdd method is not that great to use for this purpose. Since it does not guarantee that the factory runs only once, the only purpose it has is a minor optimization (minor since additions are rare anyway) in that it doesn't need to hash and find the correct bucket twice (which would happen twice if you get and set with two separate calls).
I would suggest that you check the cache first, if you do not find the value in the cache, then enter some form of critical section (lock, semaphore, etc.), re-check the cache, if still missing then fetch the value and insert into the cache.
This ensures that your backing store is only hit once; even if multiple requests get a cache miss at the same time, only the first one will actually fetch the value, the other requests will await the semaphore and then return early since they re-check the cache in the critical section.
Psuedo code (using SemaphoreSlim with count of 1, since you can await it asynchronously):
async Task<TResult> GetAsync(TKey key)
{
// Try to fetch from catch
if (cache.TryGetValue(key, out var result)) return result;
// Get some resource lock here, for example use SemaphoreSlim
// which has async wait function:
await semaphore.WaitAsync();
try
{
// Try to fetch from cache again now that we have entered
// the critical section
if (cache.TryGetValue(key, out result)) return result;
// Fetch data from source (using your HttpClient or whatever),
// update your cache and return.
return cache[key] = await FetchFromSourceAsync(...);
}
finally
{
semaphore.Release();
}
}

Try this extension method:
/// <summary>
/// Adds a key/value pair to the <see cref="ConcurrentDictionary{TKey, TValue}"/> by using the specified function
/// if the key does not already exist. Returns the new value, or the existing value if the key exists.
/// </summary>
public static async Task<TResult> GetOrAddAsync<TKey,TResult>(
this ConcurrentDictionary<TKey,TResult> dict,
TKey key, Func<TKey,Task<TResult>> asyncValueFactory)
{
if (dict.TryGetValue(key, out TResult resultingValue))
{
return resultingValue;
}
var newValue = await asyncValueFactory(key);
return dict.GetOrAdd(key, newValue);
}
Instead of dict.GetOrAdd(key,key=>something(key)), you use await dict.GetOrAddAsync(key,async key=>await something(key)). Obviously, in this situation you just write it as await dict.GetOrAddAsync(key,something), but I wanted to make it clear.
In regards to concerns about preserving the order of operations, I have the following observations:
Using the normal GetOrAdd will get the same effect if you look at the way it is implemented. I literally used the same code and made it work for async. Reference says
the valueFactory delegate is called outside the locks to avoid the
problems that can arise from executing unknown code under a lock.
Therefore, GetOrAdd is not atomic with regards to all other operations
on the ConcurrentDictionary<TKey,TValue> class
SyncRoot is not supported in ConcurrentDictionary, they use an internal locking mechanism, so locking on it is not possible. Using your own lock mechanism works only for this extension method, though. If you use another flow (using GetOrAdd for example) you will face the same problem.

Probably using a dedicated memory cache with advanced asynchronous capabilities, like the LazyCache by Alastair Crabtree, would be preferable to using a simple ConcurrentDictionary<K,V>. You would get commonly needed functionality like time-based expiration, or automatic eviction of entries that are dependent on other entries that have expired, or are dependent on mutable external resources (like files, databases etc). These features are not trivial to implement manually.
Below is a custom extension method GetOrAddAsync for ConcurrentDictionarys that have Task<TValue> values. It accepts a factory method, and ensures that the method will be invoked at most once. It also ensures that failed tasks are removed from the dictionary.
/// <summary>
/// Returns an existing task from the concurrent dictionary, or adds a new task
/// using the specified asynchronous factory method. Concurrent invocations for
/// the same key are prevented, unless the task is removed before the completion
/// of the delegate. Failed tasks are evicted from the concurrent dictionary.
/// </summary>
public static Task<TValue> GetOrAddAsync<TKey, TValue>(
this ConcurrentDictionary<TKey, Task<TValue>> source, TKey key,
Func<TKey, Task<TValue>> valueFactory)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(valueFactory);
Task<TValue> currentTask;
if (source.TryGetValue(key, out currentTask))
return currentTask;
Task<Task<TValue>> newTaskTask = new(() => valueFactory(key));
Task<TValue> newTask = null;
newTask = newTaskTask.Unwrap().ContinueWith(task =>
{
if (!task.IsCompletedSuccessfully)
source.TryRemove(KeyValuePair.Create(key, newTask));
return task;
}, default, TaskContinuationOptions.DenyChildAttach |
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default).Unwrap();
currentTask = source.GetOrAdd(key, newTask);
if (ReferenceEquals(currentTask, newTask))
newTaskTask.RunSynchronously(TaskScheduler.Default);
return currentTask;
}
This method is implemented using the Task constructor for creating a cold Task, that is started only if it is added successfully in the dictionary. Otherwise, if another thread wins the race to add the same key, the cold task is discarded. The advantage of using this technique over the simpler Lazy<Task> is that in case the valueFactory blocks the current thread, it won't block also other threads that are awaiting for the same key. The same technique can be used for implementing an AsyncLazy<T> or an AsyncExpiringLazy<T> class.
Usage example:
ConcurrentDictionary<string, Task<JsonDocument>> cache = new();
JsonDocument document = await cache.GetOrAddAsync("https://example.com", async url =>
{
string content = await _httpClient.GetStringAsync(url);
return JsonDocument.Parse(content);
});
Overload with synchronous valueFactory delegate:
public static Task<TValue> GetOrAddAsync<TKey, TValue>(
this ConcurrentDictionary<TKey, Task<TValue>> source, TKey key,
Func<TKey, TValue> valueFactory)
{
ArgumentNullException.ThrowIfNull(valueFactory);
return source.GetOrAddAsync(key, key => Task.FromResult<TValue>(valueFactory(key)));
}
Both overloads invoke the valueFactory delegate on the current thread.
If you have some reason to prefer invoking the delegate on the ThreadPool, you can just replace the RunSynchronously with the Start.
For a version of the GetOrAddAsync method that compiles on .NET versions older than .NET 6, you can look at the 3rd revision of this answer.

I solved this years ago before ConcurrentDictionary and the TPL was born. I'm in a café and don't have that original code but it went something like this.
It's not a rigorous answer but may inspire your own solution. The important thing is to return the value that was just added or exists already along with the boolean so you can fork execution.
The design lets you easily fork the race winning logic vs. the losing logic.
public bool TryAddValue(TKey key, TValue value, out TValue contains)
{
// guards etc.
while (true)
{
if (this.concurrentDic.TryAdd(key, value))
{
contains = value;
return true;
}
else if (this.concurrentDic.TryGetValue(key, out var existing))
{
contains = existing;
return false;
}
else
{
// Slipped down the rare path. The value was removed between the
// above checks. I think just keep trying because we must have
// been really unlucky.
// Note this spinning will cause adds to execute out of
// order since a very unlucky add on a fast moving collection
// could in theory be bumped again and again before getting
// lucky and getting its value added, or locating existing.
// A tiny random sleep might work. Experiment under load.
}
}
}
This could be made into an extension for ConcurrentDictionary or be a method on its own your own cache or something using locks.
Perhaps a GetOrAdd(K,V) could be used with an Object.ReferenceEquals() to check if it was added or not, instead of the spin design.
To be honest, the above code isn't the point of my answer. The power comes in the simple design of the method signature and how it affords the following:
static readonly ConcurrentDictionary<string, Task<Task<Thing>>> tasks = new();
//
var newTask = new Task<Task<Thing>>(() => GetThingAsync(thingId));
if (this.tasks.TryAddValue(thingId, newTask, out var task))
{
task.Start();
}
var thingTask = await task;
var thing = await thingTask;
It's a little quirky how a Task needs to hold a Task (if your work is async), and there's the allocations of unused Tasks to consider.
I think it's a shame Microsoft didn't ship its thread-safe collection with this method, or extract a "concurrent collection" interface.
My real implementation was a cache with sophisticated expiring inner collections and stuff. I guess you could subclass the .NET Task class and add a CreatedAt property to aid with eviction.
Disclaimer I've not tried this at all, it's off top of head, but I used this sort of design in an ultra-hi thru-put app in 2009.

Related

Is it ok to hot swap a Dictionary without synchronization that is only ever read-accessed from multiple threads?

I have a configuration repository class that roughly looks like this:
public class ConfigurationRepository // pseudo c#
{
private IDictionary<string, string> _cache = new Dictionary<string, string>();
private ConfigurationStore _configStore;
private CancellationToken cancellationToken;
public ConfigurationRepository(ConfigurationStore configStore, CancellationToken cancellationToken)
{
_configStore = configStore;
_cancellationToken = cancellationToken;
LiveCacheReload();
}
private void LiveCacheReload()
{
Task.Run(() =>
while(!_cancellationToken.IsCancellationRequested)
{
try {
_cache = new Dictionary<string, string>(_store.GetAllItems(), StringComparer.OrdinalIgnoreCase);
} catch {} // ignore
// some exponential back-off code here
}
);
}
... get methods ...
}
... where _cache is only ever accessed in a read-only manner through _cache.ContainsKey(key), _cache.Keys, and _cache[key].
This class is accessed from multiple threads. Is it ok to hot swap this Dictionary without synchronization when it is only ever read-accessed? ConfigurationProvider from Microsoft.Extensions.Configuration looks to be implemented in the same way.
It depends. If you have code which does something like:
if (_cache.ContainsKey(key))
{
var x = _cache[key];
}
that's obviously unsafe, because _cache could be re-assigned between the first and second reads.
If the consumer code only ever accesses _cache once (and creates a local copy if it needs to do multiple accesses), it's safe in the sense that you shouldn't get a crash. However you need to carefully audit every place where _cache is accessed to make sure that the code doesn't make any assumptions about _cache.
However, there's no memory barrier around reading or writing _cache, which means that a thread reading _cache may read a value which is old: the compiler, JIT and even CPU are allowed to return a value which was read some time ago. For example, in a tight loop which reads _cache on every iteration, the JIT may re-arrange instructions so that _cache is read once just before the loop, and then never re-read inside the loop. Likewise a CPU cache local to one processor core may contain an out-of-date value for _value, and the CPU is under no obligation to update this if another core writes a different value through a different cache.
To avoid this, you need a memory barrier, and the safest way to introduce one is through a lock.
So, don't be clever and try and avoid locks. It's fraught: lock-free code is really hard to write correctly, but it's very very easy to write something which appears to work, and then causes a subtle error in very particular circumstances which is impossible to track down. It's just not worth the risk.
For an eye-opening read, try Eric Lippert's post Can I skip the lock when reading an integer? (and the follow-up article linked at the bottom).

What are best practices / good patterns for managing cached async data?

I am rewriting an old app and I am trying to use async to speed it up.
The old code was doing something like this:
var value1 = getValue("key1");
var value2 = getValue("key2");
var value3 = getValue("key3");
where the getValue function was managing its own cache in a dictionary, doing something like this:
object getValue(string key) {
if (cache.ContainsKey(key)) return cache[key];
var value = callSomeHttpEndPointsAndCalculateTheValue(key);
cache.Add(key, value);
return value;
}
If I make the getValue async and I await every call to getValue, then everything works well. But it is not faster than the old version because everything is running synchronously as it used to.
If I remove the await (well, if I postpone it, but that's not the focus of this question), I finally get the slow stuff to run in parallel. But if a second call to getValue("key1") is executed before the first call has finished, I end up with executing the same slow call twice and everything is slower than the old version, because it doesn't take advantage of the cache.
Is there something like await("key1") that will only await if a previous call with "key1" is still awaiting?
EDIT (follow-up to a comment)
By "speed it up" I mean more responsive.
For example when the user selects a material in a drop down, I want to update the list of available thicknesses or colors in other drop downs and other material properties in other UI elements. Sometimes this triggers a cascade of events that requires the same getValue("key") to used more than once.
For example when the material is changed, a few functions may be called: updateThicknesses(), updateHoleOffsets(), updateMaxWindLoad(), updateMaxHoleDistances(), etc. Each function reads the values from the UI elements and decides whether to do its own slow calculations independently from the other functions. Each function can require a few http calls to calculate some parameters, and some of those parameters may be required by several functions.
The old implementation was calling the functions in sequence, so the second function would take advantage of some values cached while processing the first one. The user would see each section of the interface updating in sequence over 5-6 seconds the first time and very quickly the following times, unless the new value required some new http endpoint calls.
The new async implementation calls all the functions at the same time, so every function ends up calling the same http endpoints because their results are not yet cached.
A simple method is to cache the tasks instead of the values, this way you can await both a pending task and an already completed task to get the values.
If several parallel tasks all try to get a value using the same key, only the first will spin off the task, the others will await the same task.
Here's a simple implementation:
private Dictionary<string, Task<object>> cache = new();
public Task<object> getValueAsync(string key)
{
lock (cache)
{
if (!cache.TryGetValue(key, out var result))
cache[key] = result = callSomeHttpEndPointsAndCalculateTheValueAsync(key);
return result;
}
}
Judging by the comments the following example should probably not be used.
Since [ConcurrentDictionary]() has been mentioned, here's a version using that instead.
private ConcurrentDictionary<string, Task<object>> cache = new();
public Task<object> getValueAsync(string key)
{
return cache.GetOrAdd(key, k => callSomeHttpEndPointsAndCalculateTheValueAsync(k));
}
The method seems simpler and that alone might be grounds for switching to it, but in my experience the ConcurrentDictionary and the other ConcurrentXXX collections seems to have their niche use and seems somewhat more heavyhanded and thus slower for the basic stuff.

Is ConcurrentDictionary.GetOrAdd truly thread-safe?

I have this piece of code where I want to await on a ongoing task if that task was created for the same input. Here is minimal reproduction of what I'm doing.
private static ConcurrentDictionary<int, Task<int>> _tasks = new ConcurrentDictionary<int, Task<int>>();
private readonly ExternalService _service;
public async Task SampleTask(){
var result = await _service.DoSomething();
await Task.Delay(1000) //this task takes some time do finish
return result;
}
public async Task<int> DoTask(int key) {
var task = _tasks.GetOrAdd(key, _ => SampleTask());
var taskResult = await task;
_tasks.TryRemove(key, out task);
return taskResult;
}
I'm writing a test to ensure the same task is awaited when multiple requests want to perform the task at (roughly) the same time. I'm doing that by mocking _service and counting how many times _service.DoSomething() is being called. It should be only once if the calls to DoTask(int key) where made at roughly the same time.
However, the results show me that if I call DoTask(int key) more than once with a delay between calls of less than 1~2ms, both tasks will create and execute its on instance of SampleTask() with the second one replacing the first one in the dictionary.
Considering this, can we say that this method is truly thread-safe? Or isn't my problem a case of thread-safety per se?
To quote the documentation (emphasis mine):
For modifications and write operations to the dictionary, ConcurrentDictionary<TKey,TValue> uses fine-grained locking to ensure thread safety. (Read operations on the dictionary are performed in a lock-free manner.) However, the valueFactory delegate is called outside the locks to avoid the problems that can arise from executing unknown code under a lock. Therefore, GetOrAdd is not atomic with regards to all other operations on the ConcurrentDictionary<TKey,TValue> class.
Since a key/value can be inserted by another thread while valueFactory is generating a value, you cannot trust that just because valueFactory executed, its produced value will be inserted into the dictionary and returned. If you call GetOrAdd simultaneously on different threads, valueFactory may be called multiple times, but only one key/value pair will be added to the dictionary.
So while the dictionary is properly thread-safe, calls to the valueFactory, or _ => SampleTask() in your case, are not guaranteed to be unique. So your factory function should be able to live with that fact.
You can confirm this from the source:
public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
{
if (key == null) throw new ArgumentNullException("key");
if (valueFactory == null) throw new ArgumentNullException("valueFactory");
TValue resultingValue;
if (TryGetValue(key, out resultingValue))
{
return resultingValue;
}
TryAddInternal(key, valueFactory(key), false, true, out resultingValue);
return resultingValue;
}
As you can see, valueFactory is being called outside of TryAddInternal which is responsible of locking the dictionary properly.
However, since valueFactory is a lambda function that returns a task in your case (_ => SampleTask()), and the dictionary will not await that task itself, the function will finish quickly and just return the incomplete Task after encountering the first await (when the async state machine is set up). So unless the calls are very quickly after another, the task should be added very quickly to the dictionary and subsequent calls will reuse the same task.
If you require this to happen just once in all cases, you should consider locking on the task creation yourself. Since it will finish quickly (regardless of how long your task actually takes to resolve), locking will not hurt that much.

Use SemaphoreSlim in method without exception handling

Currently I'm struggeling with the implementation of SemaphoreSlim for "locking" "parts" of a method which has to be thread-safe. My problem is, that implementing this without having an overload of exception handling is very hard. Because when an exception is thrown before the "lock" will be released, it will stay there forever.
Here is an example:
private SemaphoreSlim _syncLock = new SemaphoreSlim(1);
private IDictionary<string, string> dict = new Dictionary<string, string>();
public async Task ProcessSomeThing(string input)
{
string someValue = await GetSomeValueFromAsyncMethod(input);
await _syncLock.WaitAsync();
dict.Add(input, someValue);
_syncLock.Release();
}
This method would throw an exception if input has the same value more than once, because an item with the same key will be added twice to the dictionary and the "lock" will not be released.
Let's assume i have a lot of _syncLock.Release(); and _syncLock.Release();, it is very hard to write the try-catch or .ContainsKey or some thing else. This would totally blow up the code... Is it possible to release the lock always when an Exception get's thrown or some term is leaved?
Hope it is clear what I'm asking/looing for.
Thank you all!
I suggest not using lock or the SemaphoreSlim. Instead, use the right tool for the job -- in this case it would seem appropriate to use a ConcurrentDictionary<TKey, Lazy<TValue>> over the use of IDictionary<string, string> and locking and semaphore's. There have been several articles about this pattern year's ago, here's one of the them. So following this suggested pattern would look like this:
private ConcurrentDictionary<string, Lazy<Task<string>>> dict =
new ConcurrentDictionary<string, Lazy<Task<string>>>();
public Task ProcessSomeThing(string input)
{
return dict.AddOrUpdate(
input,
key => new Lazy<Task<string>>(() =>
GetSomeValueFromAsyncMethod(key),
LazyThreadSafetyMode.ExecutionAndPublication),
(key, existingValue) => new Lazy<Task<string>>(() =>
GetSomeValueFromAsyncMethod(key), // unless you want the old value
LazyThreadSafetyMode.ExecutionAndPublication)).Value;
}
Ultimately this achieves the goal of thread-safety for asynchronously adding to your dictionary. And the error handling occurs as you'd expect it to, assuming that there is a try / catch in your GetSomeValueFromAsyncMethod function. A few more resources:
Why does ConcurrentDictionary.GetOrAdd(key, valueFactory) allow the valueFactory to be invoked twice?
http://softwareblog.alcedo.com/post/2012/01/11/Sometimes-being-Lazy-is-a-good-thing.aspx
Finally, I have created an example .NET fiddle to help demonstrate the idea.
You can just use lock because there is no await inside the protected region. That handles all of this.
If that was not the case you would either need to use try-finally everywhere or write a custom disposable so that you can use the scoping nature of using.

How to implement a thread-safe cache mechanism when working with collections?

Scenario:
I have a bunch of Child objects, all related to a given Parent.
I'm working on an ASP.NET MVC 3 Web Application (e.g multi-threaded)
One of the pages is a "search" page where i need to grab a given sub-set of children and "do stuff" in memory to them (calculations, ordering, enumeration)
Instead of getting each child in a seperate call, i do one call to the database to get all children for a given parent, cache the result and "do stuff" on the result.
The problem is that the "do stuff" involves LINQ operations (enumeration, adding/removing items from the collection), which when implemented using List<T> is not thread-safe.
I have read about ConcurrentBag<T> and ConcurrentDictionary<T> but not sure if i should use one of these, or implement the synchronization/locking myself.
I'm on .NET 4, so i'm utilizing ObjectCache as a singleton instance of MemoryCache.Default. I have a service layer which works with the cache, and the services accept an instance of ObjectCache, which is done via constructor DI. This way all the services share the same ObjectCache instance.
The main thread safety issue is I need to loop through the current "cached" collection, and if the child I'm working with is already there, I need to remove it and add the one I'm working with back in, this enumeration is what causes the issues.
Any recommendations?
Yes, to implement a cache efficiently, it needs a fast lookup mechanism and so List<T> is the wrong data structure out-of-the-box. A Dictionary<TKey, TValue> is an ideal data structure for a cache because it provides a way to replace:
var value = instance.GetValueExpensive(key);
with:
var value = instance.GetValueCached(key);
by using cached values in a dictionary and using the dictionary to do the heavy lifting for lookup. The caller is none the wiser.
But, if the callers could be calling from multiple threads then .NET4 provides ConcurrentDictionary<TKey, TValue> that works perfectly in this situation. But what does the dictionary cache? It seems like in your situation the dictionary key is the child and the dictionary values are the database results for that child.
OK, so now we have a thread-safe and efficient cache of database results keyed by child. What data structure should we use for the database results?
You haven't said what those results look like but since you use LINQ we know that they are at least IEnumerable<T> and maybe even List<T>. So we're back to the same problem, right? Because List<T> is not thread-safe, we can't use it for the dictionary value. Or can we?
A cache must be read-only from the point-of-view of the caller. You say with LINQ you "do stuff" like add/remove, but that makes no sense for a cached value. It only makes sense to do stuff in the implementation of the cache itself such as replacing a stale entry with new results.
The dictionary value, because it is read-only, can be a List<T> with no ill effects, even though it will be accessed from multiple threads. You can use List<T>.AsReadOnly to improve your confidence and add some compile-time safety checking.
But the important point is that List<T> is only not thread-safe if it is mutable. Since by definition a method implemented using a cache must return the same value if called multiple times (until the value is invalidated by the cache itself), the client cannot modify the returned value and so the List<T> must be frozen, effectively immutable.
If the client desperately needs to modify the cached database results and the value is a List<T>, then the only safe way to do so is to:
Make a copy
Make changes to the copy
Ask the cache to update the value
In summary, use a thread-safe dictionary for the top-level cache and an ordinary list for the cached value, being careful never to modify the contents of the last after inserting it into the cache.
Try modifying RefreshFooCache like so:
public ReadOnlyCollection<Foo> RefreshFooCache(Foo parentFoo)
{
ReadOnlyCollection<Foo> results;
try {
// Prevent other writers but allow read operations to proceed
Lock.EnterUpgradableReaderLock();
// Recheck your cache: it may have already been updated by a previous thread before we gained exclusive lock
if (_cache.Get(parentFoo.Id.ToString()) != null) {
return parentFoo.FindFoosUnderneath(uniqueUri).AsReadOnly();
}
// Get the parent and everything below.
var parentFooIncludingBelow = _repo.FindFoosIncludingBelow(parentFoo.UniqueUri).ToList();
// Now prevent both other writers and other readers
Lock.EnterWriteLock();
// Remove the cache
_cache.Remove(parentFoo.Id.ToString());
// Add the cache.
_cache.Add(parentFoo.Id.ToString(), parentFooIncludingBelow );
} finally {
if (Lock.IsWriteLockHeld) {
Lock.ExitWriteLock();
}
Lock.ExitUpgradableReaderLock();
}
results = parentFooIncludingBelow.AsReadOnly();
return results;
}
EDIT - updated to use ReadOnlyCollection instead of ConcurrentDictionary
Here's what i currently have implemented. I ran some load tests and didn't see any errors happen - what do you guys think of this implementation:
public class FooService
{
private static ReaderWriterLockSlim _lock;
private static readonly object SyncLock = new object();
private static ReaderWriterLockSlim Lock
{
get
{
if (_lock == null)
{
lock(SyncLock)
{
if (_lock == null)
_lock = new ReaderWriterLockSlim();
}
}
return _lock;
}
}
public ReadOnlyCollection<Foo> RefreshFooCache(Foo parentFoo)
{
// Get the parent and everything below.
var parentFooIncludingBelow = repo.FindFoosIncludingBelow(parentFoo.UniqueUri).ToList().AsReadOnly();
try
{
Lock.EnterWriteLock();
// Remove the cache
_cache.Remove(parentFoo.Id.ToString());
// Add the cache.
_cache.Add(parentFoo.Id.ToString(), parentFooIncludingBelow);
}
finally
{
Lock.ExitWriteLock();
}
return parentFooIncludingBelow;
}
public ReadOnlyCollection<Foo> FindFoo(string uniqueUri)
{
var parentIdForFoo = uniqueUri.GetParentId();
ReadOnlyCollection<Foo> results;
try
{
Lock.EnterReadLock();
var cachedFoo = _cache.Get(parentIdForFoo);
if (cachedFoo != null)
results = cachedFoo.FindFoosUnderneath(uniqueUri).ToList().AsReadOnly();
}
finally
{
Lock.ExitReadLock();
}
if (results == null)
results = RefreshFooCache(parentFoo).FindFoosUnderneath(uniqueUri).ToList().AsReadOnly();
}
return results;
}
}
Summary:
Each Controller (e.g each HTTP request) gets given a new instance of FooService.
FooService has a static lock, so all the HTTP requests share the same instance.
I've used ReaderWriterLockSlim to ensure multiple threads can read, but only one thread can write. I'm hoping this should avoid "read" deadlocks. If two threads happen to need to "write" at the same time, then of course one will have to wait. But the goal here is to serve the reads quickly.
The goal is to be able to find a "Foo" from the cache as long as something above it has been retrieved.
Any tips/suggestions/problems with this approach? I'm not sure if im locking the write areas. But because i need to run LINQ on the dictionary, i figured i need a read lock.

Categories