确定一些背景。我有类似的东西:
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;
}
}
我目前遇到并发问题,即多次调用Connect()
并为每个请求创建多个IConnection
。我只需要一个。它是使用IoC容器注入各种实例的。 MakeConnnection
因为它旋转了一个WCF频道而非常昂贵。
我的问题是:如何锁定每个会话的InternalConnect
次 ?我不认为锁定每个请求是正确的方法,因为每个用户可能会发生多个请求。我当然不想为每次通话锁定,因为这会给性能带来不好的效果。
我认为这样做是个坏主意:
lock(Session.SessionID)
{
// Implementation of InternalConnect
}
注意:用户名和密码重载是我在登录时才调用的。
答案 0 :(得分:10)
这只是未经测试的代码,从我的头脑中开始,但它可能有用吗?
// 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
}
您需要找到一种方法来清除mutexMap
之外的旧会话,但这不应该太难。
答案 1 :(得分:0)
我希望ninject将类创建为单例,然后将连接存储在工厂类本身中。
当您调用InternalConnect时,请检查_connection
是否为空。如果是,请新建一个新的IConnect并将其分配给_connection
答案 2 :(得分:0)
这是一个建议: 具有MakeConnection逻辑的Connection制造商对象,它以通常的方式锁定整个过程。当会话开始时,在其中存储连接制造者并在内部连接方法中调用此方法。
这就是我的意思:
public class ConnectionMaker
{
private object _lock=new object();
public IConnection MakeConnection()
{
lock(_lock)
{
//
}
}
}
然后在你的Session_Start中你可以:
Session["ConnectionMaker"]=new ConnectionMaker();
然后在你的内部连接中:
if(! IsGoodConnection(connection))
{
var connectionMaker=Session["ConnectionMaker"] as ConnectionMaker;
connection=connectionMaker.MakeConnection();
....
}
答案 3 :(得分:0)
另一种选择是直接在每个用户会话中存储一个对象。
锁定如下:
lock (Session["SessionLock"]) {
// DoStuff
}
并且可以在每个会话启动时在global.asax中创建对象
protected void Session_Start(object sender, EventArgs e)
{
Session["SessionLock"] = new object();
}
这样做意味着会话结束后会自动删除锁定对象。
答案 4 :(得分:0)
这是我使用的实用程序类,我记不清它写了多少,但我认为它基于Stephen Cleary的代码。
它处理异步(由于Nito NuGet程序包),并发(可以处理多个调用方),然后整理锁(finally子句)。您只需要给它一个唯一的键和要执行的功能即可。
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);
}
}
}
}
您将像这样使用它;
await ThingLocker.ExecuteLockedFunctionAsync("user id etc.", () => { DoThingHere(); } );
您可以给它传递异步函数的地址,这会使它看起来更整洁。