每个ASP.NET会话锁定

时间:2012-02-16 13:12:50

标签: c# asp.net

确定一些背景。我有类似的东西:

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
}

注意:用户名和密码重载是我在登录时才调用的。

5 个答案:

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

您可以给它传递异步函数的地址,这会使它看起来更整洁。