OK some background. I have something similar to this:
class ConnectionFactory
{
public IConnection Connect()
{
if (User.IsAuthenticated) {
return InternalConnect(User.Username, null);
}
return null;
}
public IConnection Connect(string username, string password)
{
return InternalConnect(username, password);
}
private IConnection InternalConnect(string username, string password)
{
IConnection connection;
var cacheKey = Session[CacheKeySessionKey] as string;
if (!string.IsNullOrEmpty(cacheKey)) {
connection = HttpCache[cacheKey] as IConnection;
}
if (!IsGoodConnection(connection) {
connection = MakeConnection(username, password); // very costly
cacheKey = Session[CacheKeySessionKey] = // some key
HttpCache[cacheKey] = connection;
}
return connection;
}
private bool IsGoodConnection(IConnection conn)
{
return conn != null && conn.IsConnected;
}
}
I'm currently running into a concurrency problem where that Connect()
is being called multiple times and creating multiple IConnection
s per request. I only need one. It is being injected using an IoC container into various instances. MakeConnnection
is very costly as it spins up a WCF channel.
My question is: How can I lock the InternalConnect
calls per session? I don't think locking per request is the right way to go as multiple requests can happen per user. I certainly don't want to lock for every call, as this will give bad performance.
I think that doing this is a bad idea:
lock(Session.SessionID)
{
// Implementation of InternalConnect
}
Note: The username and password overload is what I call only on login.
This is just untested code, from the top of my head, but it may work?
// globally declare a map of session id to mutexes
static ConcurrentDictionary<string, object> mutexMap = new ConcurrentDictionary();
// now you can aquire a lock per session as follows
object mutex = mutexMap.GetOrAdd(session.SessionId, key => new object());
lock(mutex)
{
// Do stuff with the connection
}
You would need to find a way to clear old sessions out of the mutexMap
but that shouldn't be too difficult.
This is a utility class I use, I can't remember how much of it wrote but I think it is based on code by Stephen Cleary.
It handles async (due to the Nito NuGet package), is concurrent (can handle multiple callers) and tidies up the lock afterwards (the finally clause). You just need to give it a unique key and the function to execute.
using Nito.AsyncEx;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
public static class ThingLocker
{
private static readonly ConcurrentDictionary<string, AsyncLock> locks = new ConcurrentDictionary<string, AsyncLock>();
public static async Task ExecuteLockedFunctionAsync(string key, Func<Task> func)
{
AsyncLock mutex = null;
try
{
mutex = locks.GetOrAdd(key, new AsyncLock());
using (await mutex.LockAsync())
{
await func();
}
}
finally
{
if (mutex != null)
{
locks.TryRemove(key, out var removedValue);
}
}
}
}
You'd use it like this;
await ThingLocker.ExecuteLockedFunctionAsync("user id etc.", () => { DoThingHere(); } );
You could pass it the address of an async function instead which would make it look tidier.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With