C# 在StateServer模式下从后台线程修改会话
我使用C# 在StateServer模式下从后台线程修改会话,c#,asp.net,multithreading,session,session-state,C#,Asp.net,Multithreading,Session,Session State,我使用QueueBackgroundWorkItem构建了一个在后台线程上运行管理任务的页面。将任务排入队列后,页面开始轮询页面方法,以检查任务是否已完成。但是我认为我的从工作线程到状态请求线程的通信策略是有缺陷的 为了跨线程通信,我在StateServer模式下使用会话状态的对象。它似乎在我最初的所有本地测试中都起作用,但那是使用InProc会话状态。一旦我们把它放到服务器上,它就开始出现永远挂起轮询的状态,而没有得到状态更新。代码如下: //Object for communicating
QueueBackgroundWorkItem
构建了一个在后台线程上运行管理任务的页面。将任务排入队列后,页面开始轮询页面方法,以检查任务是否已完成。但是我认为我的从工作线程到状态请求线程的通信策略是有缺陷的
为了跨线程通信,我在StateServer
模式下使用会话状态的对象。它似乎在我最初的所有本地测试中都起作用,但那是使用InProc会话状态。一旦我们把它放到服务器上,它就开始出现永远挂起轮询的状态,而没有得到状态更新。代码如下:
//Object for communicating across threads
[Serializable]
public class BackgroundTaskStatus
{
public enum BackgroundTaskStatusType
{
None=0,
Pending=1,
Started=2,
Error=3,
Complete=4
}
public BackgroundTaskStatusType Status { get; set; }
public string Message { get; set; }
}
我已经附加了调试器,我观察到的是后台线程在会话中设置
BackgroundTaskStatus
的Status
属性,但是当后续的状态轮询请求从会话中读取该对象时,属性值不变。它们似乎在会话对象的两个不同副本上运行
现在我知道状态服务器模式序列化会话,然后在将会话绑定到新请求时反序列化会话。因此,GetStatus()
和后台线程可以同时反序列化它们自己的对象副本。但是我希望后台线程的更改被序列化回同一个源,因为GetStatus()
方法不会写入会话,所以在后台线程设置它之后,它应该最终读取更新的Status
属性值
但是,似乎会话在某个点被分支并存储了我的对象的两个不同的序列化副本,或者后台线程设置的状态被覆盖,即使GetStatus()
没有写入会话。哪里出了问题
此外,像我这样传入HttpSessionState
对象是否安全,或者是否可以在后台线程完成之前销毁它(即,它的作用域是否限定为初始请求)?我以为那是一个静止的物体,但现在我对此表示怀疑。我希望这是安全的运行在一个农场,但我希望不必得到一个数据库涉及
编辑
我发现了一些可能相关的信息:
当页面将数据保存到会话时,该值将加载到HttpSessionState类托管的定制字典类中。当正在进行的请求完成时,字典的内容将刷新到状态提供程序
对我来说,这听起来像是说我的轮询请求线程在请求结束时将其整个会话序列化回状态服务器,即使它没有做任何更改。此外,我的后台线程写入的会话字典在我修改它之后永远不会被序列化回状态服务器,这是有道理的,因为它的请求已经结束。有人能证实这一点吗?我在这页上找到了一些可能相关的信息:
当页面将数据保存到会话时,该值将加载到
为测量HttpSessionState类承载的字典类而制作。
字典的内容在以下情况下刷新到状态提供程序:
正在进行的请求完成
对我来说,这听起来像是说我的轮询请求线程在请求结束时将其整个会话序列化回状态服务器,即使它没有做任何更改。此外,我的后台线程写入的会话字典在我修改它之后永远不会被序列化回状态服务器,这是有道理的,因为它的请求已经结束
我把上面的证据当作确证。我求助于将状态存储在数据库中,而不是会话中
//Class containing a reference to the Session State and
//contains the task for QueueBackgroundWorkItem
public class LocationSiteToolProcessor
{
public static string CopyingStatusKey = "LST_CopyingStatus";
private HttpSessionState _session;
public LocationSiteToolProcessor(HttpSessionState session)
{
_session = session;
}
public void CopyPage(string relativeUrl, bool overwrite, bool subPages, CancellationToken cancellationToken)
{
if(_session[CopyingStatusKey] == null || !(_session[CopyingStatusKey] is BackgroundTaskStatus))
_session[CopyingStatusKey] = new BackgroundTaskStatus();
BackgroundTaskStatus taskStatus = _session[CopyingStatusKey] as BackgroundTaskStatus;
taskStatus.Status = BackgroundTaskStatus.BackgroundTaskStatusType.Started;
try
{
DateTime start = DateTime.Now;
ElevateToWebAdmin();
var pages = LocationSiteRepository.CopyTemplatePage(relativeUrl, overwrite, subPages);
TimeSpan duration = DateTime.Now - start;
taskStatus.Message = (pages != null ? String.Format("Page copied successfully.") : String.Format("No pages were copied.")) +
" Time elapsed: " + duration.ToString("g");
taskStatus.Status = BackgroundTaskStatus.BackgroundTaskStatusType.Complete;
}
catch (Exception ex)
{
taskStatus.Message = ex.ToString();
taskStatus.Status = BackgroundTaskStatus.BackgroundTaskStatusType.Error;
}
}
}
//Code that kicks off the background thread
Session[LocationSiteToolProcessor.CopyingStatusKey] = new BackgroundTaskStatus() { Status = BackgroundTaskStatus.BackgroundTaskStatusType.Pending };
LocationSiteToolProcessor processor = new LocationSiteToolProcessor(Session);
HostingEnvironment.QueueBackgroundWorkItem(c => processor.CopyPage(relativeUrl, overwrite, subPages, c));
//Page Method to support client side status polling
[System.Web.Services.WebMethod(true)]
public static BackgroundTaskStatus GetStatus()
{
//(Modified for brevity)
BackgroundTaskStatus taskStatus = HttpContext.Current.Session[LocationSiteToolProcessor.CopyingStatusKey] as BackgroundTaskStatus;
return taskStatus;
}