【发布时间】:2016-04-29 13:42:10
【问题描述】:
我已经使用QueueBackgroundWorkItem 构建了一个在后台线程上运行管理任务的页面。将任务排队后,页面开始轮询 Page Method 以检查任务是否已完成。但我认为我从工作线程到状态请求线程的通信策略是有缺陷的。
为了跨线程进行通信,我在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; }
}
//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;
}
我已经附加了调试器,我观察到的是后台线程在会话中设置了BackgroundTaskStatus 的Status 属性,但是当随后的状态轮询请求从会话中读取该对象时,属性值不变。它们似乎在会话对象的两个不同副本上运行。
现在我知道状态服务器模式会序列化会话,然后在将会话绑定到新请求时反序列化会话。因此GetStatus() 和后台线程可以反序列化他们自己的对象的同步副本。但我希望后台线程的更改被序列化回相同的来源,并且由于GetStatus() 方法不会写入会话,它最终应该在后台线程设置后读取更新的Status 属性值。
但是,似乎会话在某个时候被分支并且正在存储我的对象的两个不同的序列化副本,或者后台线程设置的Status 被覆盖,即使GetStatus() 没有写入到会议。哪里出错了?
此外,像我正在做的那样传入HttpSessionState 对象是否安全,或者它可以在后台线程完成之前被销毁(即它是否仅限于初始请求)?我的印象是它是一个静态对象,但现在我对此表示怀疑。我希望它可以安全地在农场上运行,但我希望不必涉及数据库。
编辑
我在this page 上找到了一些可能相关的信息:
当页面将数据保存到 Session 时,该值被加载到由 HttpSessionState 类托管的定制字典类中。当正在进行的请求完成时,字典的内容会刷新到状态提供程序。
对我来说,这听起来像是在说我的轮询请求线程确实在其请求结束时将其整个会话序列化回状态服务器,即使它没有进行任何更改.此外,有理由认为,我的后台线程写入的会话字典在我修改它之后永远不会序列化回状态服务器,因为它的请求已经结束。谁能证实这一点?
【问题讨论】:
标签: c# asp.net multithreading session session-state