asp.net mvc Session RedisSessionStateProvider锁的实现

最近项目用到了RedisSessionStateProvider来保存session,发现比内存session慢,后来慢慢了解,发现asp.net session是有锁的。我在文章 你的项目真的需要Session吗? redis保存session性能怎么样?也提到一些观点,本来打算在那篇文章补充一些类容,后来想了一下,还是重写一个短文吧。有关session 管道流程大家 可以参考 Asp.net Session认识加强-Session究竟是如何存储你知道吗?

我们的mvc程序都是有路由信息,那么就离不开UrlRoutingModule 该code如下:

namespace System.Web.Routing {
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.CompilerServices;
    using System.Web.Security;

    [TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
    public class UrlRoutingModule : IHttpModule {
        private static readonly object _contextKey = new Object();
        private static readonly object _requestDataKey = new Object();
        private RouteCollection _routeCollection;

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly",
            Justification = "This needs to be settable for unit tests.")]
        public RouteCollection RouteCollection {
            get {
                if (_routeCollection == null) {
                    _routeCollection = RouteTable.Routes;
                }
                return _routeCollection;
            }
            set {
                _routeCollection = value;
            }
        }

        protected virtual void Dispose() {
        }

        protected virtual void Init(HttpApplication application) {

            //
            // Check if this module has been already addded
            if (application.Context.Items[_contextKey] != null) {
                return; // already added to the pipeline
            }
            application.Context.Items[_contextKey] = _contextKey;

            // Ideally we would use the MapRequestHandler event.  However, MapRequestHandler is not available
            // in II6 or IIS7 ISAPI Mode.  Instead, we use PostResolveRequestCache, which is the event immediately
            // before MapRequestHandler.  This allows use to use one common codepath for all versions of IIS.
            application.PostResolveRequestCache += OnApplicationPostResolveRequestCache;
        }

        private void OnApplicationPostResolveRequestCache(object sender, EventArgs e) {
            HttpApplication app = (HttpApplication)sender;
            HttpContextBase context = new HttpContextWrapper(app.Context);
            PostResolveRequestCache(context);
        }

        [Obsolete("This method is obsolete. Override the Init method to use the PostMapRequestHandler event.")]
        public virtual void PostMapRequestHandler(HttpContextBase context) {
            // Backwards compat with 3.5 which used to have code here to Rewrite the URL
        }

        public virtual void PostResolveRequestCache(HttpContextBase context) {
            // Match the incoming URL against the route table
            RouteData routeData = RouteCollection.GetRouteData(context);

            // Do nothing if no route found
            if (routeData == null) {
                return;
            }

            // If a route was found, get an IHttpHandler from the route's RouteHandler
            IRouteHandler routeHandler = routeData.RouteHandler;
            if (routeHandler == null) {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        SR.GetString(SR.UrlRoutingModule_NoRouteHandler)));
            }

            // This is a special IRouteHandler that tells the routing module to stop processing
            // routes and to let the fallback handler handle the request.
            if (routeHandler is StopRoutingHandler) {
                return;
            }

            RequestContext requestContext = new RequestContext(context, routeData);

            // Dev10 766875    Adding RouteData to HttpContext
            context.Request.RequestContext = requestContext;

            IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
            if (httpHandler == null) {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentUICulture,
                        SR.GetString(SR.UrlRoutingModule_NoHttpHandler),
                        routeHandler.GetType()));
            }

            if (httpHandler is UrlAuthFailureHandler) {
                if (FormsAuthenticationModule.FormsAuthRequired) {
                    UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
                    return;
                }
                else {
                    throw new HttpException(401, SR.GetString(SR.Assess_Denied_Description3));
                }
            }

            // Remap IIS7 to our handler
            context.RemapHandler(httpHandler);
        }

        #region IHttpModule Members
        void IHttpModule.Dispose() {
            Dispose();
        }

        void IHttpModule.Init(HttpApplication application) {
            Init(application);
        }
        #endregion
    }
}

在PostResolveRequestCache方法中   IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); 这么一句。这里的routeHandler其实默认是MvcRouteHandler,所以智力其实是调用MvcRouteHandler的GetHttpHandler方法

MvcRouteHandler的code:

// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

using System.Web.Mvc.Properties;
using System.Web.Routing;
using System.Web.SessionState;

namespace System.Web.Mvc
{
    public class MvcRouteHandler : IRouteHandler
    {
        private IControllerFactory _controllerFactory;

        public MvcRouteHandler()
        {
        }

        public MvcRouteHandler(IControllerFactory controllerFactory)
        {
            _controllerFactory = controllerFactory;
        }

        protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
            return new MvcHandler(requestContext);
        }

        protected virtual SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext)
        {
            string controllerName = (string)requestContext.RouteData.Values["controller"];
            if (String.IsNullOrWhiteSpace(controllerName))
            {
                throw new InvalidOperationException(MvcResources.MvcRouteHandler_RouteValuesHasNoController);
            }

            IControllerFactory controllerFactory = _controllerFactory ?? ControllerBuilder.Current.GetControllerFactory();
            return controllerFactory.GetControllerSessionBehavior(requestContext, controllerName);
        }

        #region IRouteHandler Members

        IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
        {
            return GetHttpHandler(requestContext);
        }

        #endregion
    }
}

在MvcRouteHandler中GetHttpHandler设置SessionStateBehavior:

protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
return new MvcHandler(requestContext);
}

SessionStateBehavior的值默认来源于DefaultControllerFactory的GetControllerSessionBehavior方法,有SessionStateAttribute特性就取其值,否者默认的SessionStateBehavior.Default


 SessionStateBehavior IControllerFactory.GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
        {
            if (requestContext == null)
            {
                throw new ArgumentNullException("requestContext");
            }
            if (String.IsNullOrEmpty(controllerName))
            {
                throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
            }

            Type controllerType = GetControllerType(requestContext, controllerName);
            return GetControllerSessionBehavior(requestContext, controllerType);
        }
        
        protected internal virtual SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType)
        {
            if (controllerType == null)
            {
                return SessionStateBehavior.Default;
            }

            return _sessionStateCache.GetOrAdd(
                controllerType,
                type =>
                {
                    var attr = type.GetCustomAttributes(typeof(SessionStateAttribute), inherit: true)
                        .OfType<SessionStateAttribute>()
                        .FirstOrDefault();

                    return (attr != null) ? attr.Behavior : SessionStateBehavior.Default;
                });
        }

那么HttpContext.SetSessionStateBehavior方法又是如何实现的:

  internal SessionStateBehavior SessionStateBehavior { get; set; }

        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
          Justification = "An internal property already exists. This method does additional work.")]
        public void SetSessionStateBehavior(SessionStateBehavior sessionStateBehavior) {
            if (_notificationContext != null && _notificationContext.CurrentNotification >= RequestNotification.AcquireRequestState) {
                throw new InvalidOperationException(SR.GetString(SR.Invoke_before_pipeline_event, "HttpContext.SetSessionStateBehavior", "HttpApplication.AcquireRequestState"));
            }

            SessionStateBehavior = sessionStateBehavior;
        }

其实很简单,就是设置了一个属性,这里还有一个ReadOnlySessionState属性很重要,他需要读取SessionStateBehavior属性。由于MvcHandler 默认继承了IRequiresSessionState接口但是没有继承IReadOnlySessionState,

所以默认RequiresSessionState为true,ReadOnlySessionState为false

public IHttpHandler Handler {
            get { return _handler;}
            set {
                _handler = value;
                _requiresSessionStateFromHandler = false;
                _readOnlySessionStateFromHandler = false;
                InAspCompatMode = false;
                if (_handler != null) {
                    if (_handler is IRequiresSessionState) {
                        _requiresSessionStateFromHandler = true;
                    }
                    if (_handler is IReadOnlySessionState) {
                        _readOnlySessionStateFromHandler = true;
                    }
                    Page page = _handler as Page;
                    if (page != null && page.IsInAspCompatMode) {
                        InAspCompatMode = true;
                    }
                }
            }
        }
        
// session state support
        private bool _requiresSessionStateFromHandler;
        internal bool RequiresSessionState {
            get {
                switch (SessionStateBehavior) {
                    case SessionStateBehavior.Required:
                    case SessionStateBehavior.ReadOnly:
                        return true;
                    case SessionStateBehavior.Disabled:
                        return false;
                    case SessionStateBehavior.Default:
                    default:
                        return _requiresSessionStateFromHandler;
                }
            }
        }

        private bool _readOnlySessionStateFromHandler;
        internal bool ReadOnlySessionState {
            get {
                switch (SessionStateBehavior) {
                    case SessionStateBehavior.ReadOnly:
                        return true;
                    case SessionStateBehavior.Required:
                    case SessionStateBehavior.Disabled:
                        return false;
                    case SessionStateBehavior.Default:
                    default:
                        return _readOnlySessionStateFromHandler;
                }
            }
        }        

在SessionStateModule的GetSessionStateItem方法里面有如下code:


这里我们用的是RedisSessionStateProvider,其code如下:

//
// Copyright (c) Microsoft Corporation.  All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
//

using System;
using System.Web;
using System.Web.SessionState;

namespace Microsoft.Web.Redis
{
    public class RedisSessionStateProvider : SessionStateStoreProviderBase
    {
        // We want to release lock (if exists) during EndRequest, to do that we need session-id and lockId but EndRequest do not have these parameter passed to it. 
        // So we are going to store 'sessionId' and 'lockId' when we acquire lock. so that EndRequest can release lock at the end. 
        // If we removed the lock before that than we will clear these by our self so that EndRequest won't do that again (only Release item exclusive does that).
        internal string sessionId;
        internal object sessionLockId;
        private const int FROM_MIN_TO_SEC = 60;
        
        internal static ProviderConfiguration configuration;
        internal static object configurationCreationLock = new object();
        internal ICacheConnection cache;

        private static object _lastException = new object();

        /// <summary>
        /// We do not want to throw exception from session state provider because this will break customer application and they can't get chance to handel it.
        /// So if exception occurs because of some problem we store it in HttpContext using a key that we know and return null to customer. Now, when customer
        /// get null from any of session operation they should call this method to identify if there was any exception and because of that got null.
        /// </summary>
        public static Exception LastException
        {
            get 
            {
                if (HttpContext.Current != null)
                {
                    return (Exception) HttpContext.Current.Items[_lastException];
                }
                return null;
            }

            set
            {
                if (HttpContext.Current != null)
                {
                    HttpContext.Current.Items[_lastException] = value;
                }             
            }
        }

        private void GetAccessToStore(string id) 
        {
            if (cache == null)
            {
                cache = new RedisConnectionWrapper(configuration, id);
            }
            else
            {
                cache.Keys.RegenerateKeyStringIfIdModified(id, configuration.ApplicationName);
            }
        }

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            if (config == null)
            { 
                throw new ArgumentNullException("config");
            }
            
            if (name == null || name.Length == 0)
            {
                name = "MyCacheStore";
            }
            
            if (String.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "Redis as a session data store");
            }

            base.Initialize(name, config);

            // If configuration exists then use it otherwise read from config file and create one
            if (configuration == null)
            {
                lock (configurationCreationLock) 
                {
                    if (configuration == null)
                    {
                        configuration = ProviderConfiguration.ProviderConfigurationForSessionState(config);
                    }
                }
            }
        }

        public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
        {
            //We don't receive notifications when cache items expire, so we can't support Session_OnEnd.
            return false;
        }

        public override void InitializeRequest(HttpContext context)
        {
            //Not need. Initializing in 'Initialize method'.
        }

        public override void Dispose()
        {
            //Not needed. Cleanup is done in 'EndRequest'.
        }

        public override void EndRequest(HttpContext context)
        {
            try
            {
                // This check is required for unit tests to work
                int sessionTimeoutInSeconds;
                if (context != null && context.Session != null)
                {
                    sessionTimeoutInSeconds = context.Session.Timeout * FROM_MIN_TO_SEC;
                }
                else
                {
                    sessionTimeoutInSeconds = (int)configuration.SessionTimeout.TotalSeconds;
                }

                if (sessionId != null && sessionLockId != null)
                {
                    GetAccessToStore(sessionId);
                    cache.TryReleaseLockIfLockIdMatch(sessionLockId, sessionTimeoutInSeconds);
                    LogUtility.LogInfo("EndRequest => Session Id: {0}, Session provider object: {1} => Lock Released with lockId {2}.", sessionId, this.GetHashCode(), sessionLockId);
                    sessionId = null;
                    sessionLockId = null;
                }
                cache = null;
            }
            catch (Exception e)
            {
                LogUtility.LogError("EndRequest => {0}", e.ToString());
                LastException = e;
                if (configuration.ThrowOnError)
                {
                    throw;
                }
            }
        }

        public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
        {
            //Creating empty session store data and return it. 
            LogUtility.LogInfo("CreateNewStoreData => Session provider object: {0}.", this.GetHashCode());
            return new SessionStateStoreData(new ChangeTrackingSessionStateItemCollection(), new HttpStaticObjectsCollection(), timeout);
        }
        
        public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
        {
            try
            {
                if (LastException == null)
                {
                    LogUtility.LogInfo("CreateUninitializedItem => Session Id: {0}, Session provider object: {1}.", id, this.GetHashCode());
                    ISessionStateItemCollection sessionData = new ChangeTrackingSessionStateItemCollection();
                    sessionData["SessionStateActions"] = SessionStateActions.InitializeItem;
                    GetAccessToStore(id);
                    // Converting timout from min to sec
                    cache.Set(sessionData, (timeout * FROM_MIN_TO_SEC));
                }
            }
            catch (Exception e)
            {
                LogUtility.LogError("CreateUninitializedItem => {0}", e.ToString());
                LastException = e;
                if (configuration.ThrowOnError)
                {
                    throw;
                }
            }
        }
        
        public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {
            LogUtility.LogInfo("GetItem => Session Id: {0}, Session provider object: {1}.", id, this.GetHashCode());
            return GetItemFromSessionStore(false, context, id, out locked, out lockAge, out lockId, out actions);
        }

        public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {
            LogUtility.LogInfo("GetItemExclusive => Session Id: {0}, Session provider object: {1}.", id, this.GetHashCode());
            return GetItemFromSessionStore(true, context, id, out locked, out lockAge, out lockId, out actions);
        }

        private SessionStateStoreData GetItemFromSessionStore(bool isWriteLockRequired, HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {
            try
            {
                SessionStateStoreData sessionStateStoreData = null;
                locked = false;
                lockAge = TimeSpan.Zero;
                lockId = 0;
                actions = SessionStateActions.None;
                if (id == null)
                {
                    return null;
                }
                GetAccessToStore(id);
                ISessionStateItemCollection sessionData = null;
            
                int sessionTimeout;
                bool isLockTaken = false;
                //Take read or write lock and if locking successful than get data in sessionData and also update session timeout
                if (isWriteLockRequired)
                {
                    isLockTaken = cache.TryTakeWriteLockAndGetData(DateTime.Now, (int)configuration.RequestTimeout.TotalSeconds, out lockId, out sessionData, out sessionTimeout);
                    sessionId = id; // signal that we have to remove lock in EndRequest
                    sessionLockId = lockId; // save lockId for EndRequest
                }
                else
                {
                    isLockTaken = cache.TryCheckWriteLockAndGetData(out lockId, out sessionData, out sessionTimeout);
                }

                if (isLockTaken)
                {
                    locked = false;
                    LogUtility.LogInfo("GetItemFromSessionStore => Session Id: {0}, Session provider object: {1} => Lock taken with lockId: {2}", id, this.GetHashCode(), lockId);
                }
                else
                {
                    sessionId = null;
                    sessionLockId = null;
                    locked = true;
                    LogUtility.LogInfo("GetItemFromSessionStore => Session Id: {0}, Session provider object: {1} => Can not lock, Someone else has lock and lockId is {2}", id, this.GetHashCode(), lockId);
                }

                // If locking is not successful then do not return any result just return lockAge, locked=true and lockId.
                // ASP.NET tries to acquire lock again in 0.5 sec by calling this method again. Using lockAge it finds if 
                // lock has been taken more than http request timeout than ASP.NET calls ReleaseItemExclusive and calls this method again to get lock.
                if (locked) 
                {
                    lockAge = cache.GetLockAge(lockId);
                    return null;
                }

                if (sessionData == null)
                {
                    // If session data do not exists means it might be exipred and removed. So return null so that asp.net can call CreateUninitializedItem and start again.
                    // But we just locked the record so first release it
                    ReleaseItemExclusive(context, id, lockId);
                    return null;
                }
            
                // Restore action flag from session data
                if (sessionData["SessionStateActions"] != null) 
                {
                    actions = (SessionStateActions)Enum.Parse(typeof(SessionStateActions), sessionData["SessionStateActions"].ToString());
                }

                //Get data related to this session from sessionDataDictionary and populate session items
                sessionData.Dirty = false;
                sessionStateStoreData = new SessionStateStoreData(sessionData, new HttpStaticObjectsCollection(), sessionTimeout);
                return sessionStateStoreData;
            }
            catch (Exception e)
            {
                LogUtility.LogError("GetItemFromSessionStore => {0}", e.ToString());
                locked = false;
                lockId = null;
                lockAge = TimeSpan.Zero;
                actions = 0;
                LastException = e;
                if (configuration.ThrowOnError)
                {
                    throw;
                }
                return null;
            }
        }
       
        public override void ResetItemTimeout(HttpContext context, string id) 
        {
            try
            {
                if (LastException == null)
                {
                    LogUtility.LogInfo("ResetItemTimeout => Session Id: {0}, Session provider object: {1}.", id, this.GetHashCode());
                    GetAccessToStore(id);
                    cache.UpdateExpiryTime((int)configuration.SessionTimeout.TotalSeconds);
                    cache = null;
                }
            }
            catch (Exception e)
            {
                LogUtility.LogError("ResetItemTimeout => {0}", e.ToString());
                LastException = e;
                if (configuration.ThrowOnError)
                {
                    throw;
                }
            }
        }

        public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
        {
            try
            {
                if (LastException == null && lockId != null)
                {
                    LogUtility.LogInfo("RemoveItem => Session Id: {0}, Session provider object: {1}.", id, this.GetHashCode());
                    GetAccessToStore(id);
                    cache.TryRemoveAndReleaseLockIfLockIdMatch(lockId);
                }
            }
            catch (Exception e)
            {
                LogUtility.LogError("RemoveItem => {0}", e.ToString());
                LastException = e;
                if (configuration.ThrowOnError)
                {
                    throw;
                }
            }
        }

        public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
        {
            try
            {
                // This check is required for unit tests to work
                int sessionTimeoutInSeconds;
                if (context != null && context.Session != null)
                {
                    sessionTimeoutInSeconds = context.Session.Timeout * FROM_MIN_TO_SEC;
                }
                else
                {
                    sessionTimeoutInSeconds = (int)configuration.SessionTimeout.TotalSeconds;
                }

                if (LastException == null && lockId != null)
                {
                    LogUtility.LogInfo("ReleaseItemExclusive => Session Id: {0}, Session provider object: {1} => For lockId: {2}.", id, this.GetHashCode(), lockId);
                    GetAccessToStore(id);
                    cache.TryReleaseLockIfLockIdMatch(lockId, sessionTimeoutInSeconds);
                    // Either already released lock successfully inside above if block
                    // Or we do not hold lock so we should not release it.
                    sessionId = null;
                    sessionLockId = null;
                }
            }
            catch (Exception e)
            {
                LogUtility.LogError("ReleaseItemExclusive => {0}", e.ToString());
                LastException = e;
                if (configuration.ThrowOnError)
                {
                    throw;
                }
            }
        }
        
        public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
        {
            try
            {
                if (LastException == null)
                {
                    GetAccessToStore(id);
                    // If it is new record
                    if (newItem)
                    {
                        ISessionStateItemCollection sessionItems = null;
                        if (item != null && item.Items != null)
                        {
                            sessionItems = item.Items;
                        }
                        else
                        {
                            sessionItems = new ChangeTrackingSessionStateItemCollection();
                        }

                        if (sessionItems["SessionStateActions"] != null)
                        {
                            sessionItems.Remove("SessionStateActions");
                        }

                        // Converting timout from min to sec
                        cache.Set(sessionItems, (item.Timeout * FROM_MIN_TO_SEC));
                        LogUtility.LogInfo("SetAndReleaseItemExclusive => Session Id: {0}, Session provider object: {1} => created new item in session.", id, this.GetHashCode());
                    } // If update if lock matches
                    else
                    {
                        if (item != null && item.Items != null)
                        {
                            if (item.Items["SessionStateActions"] != null)
                            {
                                item.Items.Remove("SessionStateActions");
                            }
                            // Converting timout from min to sec
                            cache.TryUpdateAndReleaseLockIfLockIdMatch(lockId, item.Items, (item.Timeout * FROM_MIN_TO_SEC));
                            LogUtility.LogInfo("SetAndReleaseItemExclusive => Session Id: {0}, Session provider object: {1} => updated item in session.", id, this.GetHashCode());
                        }
                    }
                }
            }
            catch (Exception e)
            {
                LogUtility.LogError("SetAndReleaseItemExclusive => {0}", e.ToString());
                LastException = e;
                if (configuration.ThrowOnError)
                {
                    throw;
                }
            }
        }
    }
}

其中GetItem和GetItemExclusive都是调用GetItemFromSessionStore方法,如果入口是GetItem调用TryCheckWriteLockAndGetData方法(不会发生锁),入口时GetItemExclusive调用TryTakeWriteLockAndGetData方法(会有锁),但是这2个方法都会修改Session的SessionTimeout值。

TryTakeWriteLockAndGetData方法的实现如下:


运行结果如下:


TryCheckWriteLockAndGetData的实现如下:


 

在GetItemFromSessionStore方法中如果获取ISessionStateItemCollection实例为null,我们调用 ReleaseItemExclusive(context, id, lockId)方法来释放锁(前提是前面调用TryTakeWriteLockAndGetData已经获取lockId),一般这个方法都不会执行的,现在我们知道默认情况下载装在session的时候就会锁,如果session实例为null我们会释放我们的锁

那么这个锁又是是么时候释放的了?在 app.ReleaseRequestState += new EventHandler(this.OnReleaseState);会调用我们这里的SetAndReleaseItemExclusive方法,默认情况下它会释放我们的锁

运行该方法结果如下:


其实现code如下:


RedisSessionStateProvider为了保证性能,在EndRequest里面还会尝试 释放锁



到现在我们知道默认加载Session数据的时候会加锁,在ReleaseRequestState事件默认解锁

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值