C# 每个ASP.NET会话锁定
好的,一些背景资料。我有类似的想法:C# 每个ASP.NET会话锁定,c#,asp.net,C#,Asp.net,好的,一些背景资料。我有类似的想法: class ConnectionFactory { public IConnection Connect() { if (User.IsAuthenticated) { return InternalConnect(User.Username, null); } return null; } public IConnection Connect(stri
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
s。我只需要一个。它使用IoC容器注入到各种实例中MakeConnection
成本非常高,因为它会启动WCF频道
我的问题是:如何锁定每个会话的InternalConnect
调用?我不认为每个请求锁定是正确的方式,因为每个用户都可能发生多个请求。我当然不想每次调用都锁定,因为这会带来糟糕的性能
我认为这样做是个坏主意:
lock(Session.SessionID)
{
// Implementation of InternalConnect
}
注意:用户名和密码重载是我仅在登录时调用的。我会让ninject将类创建为单例,然后将连接存储在factory类本身中
调用InternalConnect时,请检查
\u connection
是否为空。如果它是新的,请创建一个新的IConnect并将其分配给_connection这只是未经测试的代码,从我的头脑来看,但它可能会工作吗
// 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
}
//全局声明会话id到互斥体的映射
静态ConcurrentDictionary mutexMap=新ConcurrentDictionary();
//现在,您可以按如下方式获得每个会话的锁
objectmutex=mutexMap.GetOrAdd(session.SessionId,key=>newobject());
锁(互斥)
{
//用连接做一些事情
}
您需要找到一种方法从mutexMap
中清除旧会话,但这应该不会太困难。这里有一个建议:
具有包含MakeConnection逻辑的Connection maker对象,并以通常的方式锁定整个过程。当会话启动时,在其中存储Connection maker,并在internal connect method中调用此方法
我的意思是:
public class ConnectionMaker
{
private object _lock=new object();
public IConnection MakeConnection()
{
lock(_lock)
{
//
}
}
}
然后,在会话开始时,您可以:
Session["ConnectionMaker"]=new ConnectionMaker();
然后在内部连接中:
if(! IsGoodConnection(connection))
{
var connectionMaker=Session["ConnectionMaker"] as ConnectionMaker;
connection=connectionMaker.MakeConnection();
....
}
另一种选择是在每个用户会话中直接存储一个对象 锁的外观如下所示:
lock (Session["SessionLock"]) {
// DoStuff
}
当每个会话启动时,您可以在global.asax中创建对象
protected void Session_Start(object sender, EventArgs e)
{
Session["SessionLock"] = new object();
}
这样做意味着一旦会话结束,锁对象就会被自动删除。这是我使用的一个实用程序类,我不记得它写了多少,但我认为它是基于Stephen Cleary的代码 它处理异步(由于NITONUGET包),是并发的(可以处理多个调用者),并在之后整理锁(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);
}
}
}
}
您可以将异步函数的地址传递给它,这样会使它看起来更整洁。您所说的isgoodconnection是什么意思?@Beatles1692-这只是一种检查其是否为空或是否断开连接的方法。您使用的是什么IoC容器?你能显示你的IoC配置吗?为什么你不能用通常的方式锁定这个方法。我的意思是使用一个对象来持有锁,这样在第一个线程完成其工作之前,其他线程就不能创建连接,然后如果连接状态保持有效,就不会生成其他连接?@AnthonyShaw-这是一个围绕ninject的抽象,它绑定到
Connect()
方法。这就是我基本上正在做的。ConnectionFactory
是唯一一个知道连接是否为空的方法。我试图将其排除在global.asax之外,因为这都在一个库中。因此,Session\u Start
不适用。您可以在第一次访问库时将其添加到会话中。例如,在登录过程中,您是否可以将锁定对象添加到用户会话中?@rdans,这只适用于内存中的会话存储,因为其他会话提供程序将在保存过程中序列化对象。这确保对密钥的所有请求都是序列化的,这意味着(我相信)要保护的每个代码块都需要一个组合键,该组合键标识会话和代码块-“[SessionId]+[CodeBlockId]”,否则不相关的受保护代码块将永远不允许并行运行。我有这个权利吗?…还有第二个问题:为什么需要异步方面?锁不是锁吗,不管它运行在哪个线程上?对不起,关于这个问题的所有知识都是从我脑子里垃圾收集出来的。我记得异步是有原因的,但我想不起来了。
await ThingLocker.ExecuteLockedFunctionAsync("user id etc.", () => { DoThingHere(); } );