Concurrent Requests for Writeable Session Variables

在之前有討論到不同網頁使用到 Writeable Session 時會卡來卡去的狀況 Lock or Blocking(使用Session要小心網頁會被 卡住 哦!),

或是 Web Service 如果開啟 Session 存取的話,預設是 Writeable Session 模式(設定 WebService 使用 ReadOnly Session)。

解法除了設定 SessionStateBehavior.ReadOnly 外,

When a Single ASP.NET Client makes Concurrent Requests for Writeable Session Variables 這篇還提供2個方式,

1.降低 Session Lock Check 的時間

2.實作 Lockless SessionStateStoreProvider

維護舊系統的朋友,如果改了 ReadOnly 又怕會影響到什麼功能,可以參考看看哦!

在寫入  Session 時會造成 500ms 的 Delay 時間(Storing Anything in ASP.NET Session Causes 500ms Delays),

如果一定會寫到 Writeable Session 時,有以下2個方式,

1.降低 Session Lock Check 的時間

可以在 Global.asax.cs 的 Application_Start Method 去設定預設值,如下,

protected void Application_Start(object sender, EventArgs e)
{
	var sessionStateModuleType = typeof(SessionStateModule);
	var pollingIntervalFieldInfo = sessionStateModuleType.GetField("LOCKED_ITEM_POLLING_INTERVAL", BindingFlags.NonPublic | BindingFlags.Static);
	var orgpollingInterval = pollingIntervalFieldInfo.GetValue(null);
	pollingIntervalFieldInfo.SetValue(null, 30); // default 500ms
	var pollingDeltaFieldInfo = sessionStateModuleType.GetField("LOCKED_ITEM_POLLING_DELTA", BindingFlags.NonPublic | BindingFlags.Static);
	var orgPollingDelta = pollingDeltaFieldInfo.GetValue(null);
	pollingDeltaFieldInfo.SetValue(null, TimeSpan.FromMilliseconds(15.0)); // default 250ms
}

所以每次 request 時,可以發現 Polling Interval 從 500ms 改成 30ms, Polling Delta 從 250ms 改成 15ms,如下,

2.實作 Lockless SessionStateStoreProvider

public class LocklessInProcSessionStateStore : SessionStateStoreProviderBase
{
	private SessionStateStoreProviderBase _store;

	public override void Initialize(string name, NameValueCollection config)
	{
		base.Initialize(name, config);

		var storeType = typeof(SessionStateStoreProviderBase).Assembly.GetType("System.Web.SessionState.InProcSessionStateStore");
		_store = (SessionStateStoreProviderBase)Activator.CreateInstance(storeType);
		_store.Initialize(name, config);
	}

	public override void Dispose()
	{
		_store.Dispose();
	}

	public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
	{
		return _store.SetItemExpireCallback(expireCallback);
	}

	public override void InitializeRequest(HttpContext context)
	{
		_store.InitializeRequest(context);
	}

	public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId,
		out SessionStateActions actions)
	{
		var returnValue = _store.GetItem(context, id, out locked, out lockAge, out lockId, out actions);
		if (returnValue == null && lockId != null)
		{
			_store.ReleaseItemExclusive(context, id, lockId);
			returnValue = _store.GetItem(context, id, out locked, out lockAge, out lockId, out actions);
		}

		return returnValue;
	}

	public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge,
		out object lockId, out SessionStateActions actions)
	{
		var returnValue = _store.GetItemExclusive(context, id, out locked, out lockAge, out lockId, out actions);
		if (returnValue == null && lockId != null)
		{
			_store.ReleaseItemExclusive(context, id, lockId);
			returnValue = _store.GetItemExclusive(context, id, out locked, out lockAge, out lockId, out actions);
		}

		return returnValue;
	}

	public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
	{
		_store.ReleaseItemExclusive(context, id, lockId);
	}

	public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
	{
		_store.SetAndReleaseItemExclusive(context, id, item, lockId, newItem);
	}

	public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
	{
		_store.RemoveItem(context, id, lockId, item);
	}

	public override void ResetItemTimeout(HttpContext context, string id)
	{
		_store.ResetItemTimeout(context, id);
	}

	public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
	{
		return _store.CreateNewStoreData(context, timeout);
	}

	public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
	{
		_store.CreateUninitializedItem(context, id, timeout);
	}

	public override void EndRequest(HttpContext context)
	{
		_store.EndRequest(context);
	}

}

在 web.config 中加入設定自定的 SessionState,如下,

<sessionState mode="Custom" customProvider="LocklessInProcSessionStateStore" cookieless="false" timeout="1" >
  <providers>
	<add name="LocklessInProcSessionStateStore" 
		 type="你專案的namespace.LocklessInProcSessionStateStore"/>
  </providers>
</sessionState>

有需要的朋友可以試看看哦! 特別是那種維護舊系統的朋友,如果改了 ReadOnly 又怕會影響到什麼功能,就可以用 方法1 哦!  

參考資料

When a Single ASP.NET Client makes Concurrent Requests for Writeable Session Variables

設定 WebService 使用 ReadOnly Session

使用Session要小心網頁會被 卡住 哦!

Hi, 

亂馬客Blog已移到了 「亂馬客​ : Re:從零開始的軟體開發生活

請大家繼續支持 ^_^