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(); } );