Reputation: 21088
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!
Upvotes: 2
Views: 2205
Reputation: 24525
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:
Finally, I have created an example .NET fiddle to help demonstrate the idea.
Upvotes: 2
Reputation: 171178
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
.
Upvotes: 2