BendEg
BendEg

Reputation: 21088

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!

Upvotes: 2

Views: 2205

Answers (2)

David Pine
David Pine

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

usr
usr

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

Related Questions