在StateServer模式下从后台线程修改会话

时间:2016-01-22 20:33:11

标签: c# asp.net multithreading session session-state

我已经使用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; }
}
//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;
}

我已经附加了调试器,我观察到的是后台线程在会话中设置Status的{​​{1}}属性,但是当后续状态轮询请求读取时来自会话的该对象,属性值不变。它们似乎在会话对象的两个不同副本上运行。

现在我知道状态服务器模式序列化会话,然后在将会话绑定到新请求时反序列化会话。因此,BackgroundTaskStatus和后台线程可以反序列化它们自己的对象副本。但是我期待后台线程的更改被序列化回同一个源,并且由于GetStatus()方法不会写入会话,它最终应该读取更新的{{1后台线程设置后的属性值。

但是,似乎会话在某个时刻被分支并存储了我的对象的两个不同的序列化副本,或者后台线程设置的GetStatus()被覆盖,即使Status没有写入会话。有人可以帮我理解它出了什么问题吗?另外,传递Status对象是否安全,就像我正在做的那样,或者可以在后台线程完成之前将其销毁(即它是否限定为初始请求)?我觉得这是一个静态的对象,但现在我对此表示怀疑。我希望这在农场上运行是安全的,但我希望不必涉及数据库。

修改

我在this page找到了一些可能相关的信息:

  

当页面将数据保存到Session时,该值将加载到由HttpSessionState类托管的定制词典类中。当正在进行的请求完成时,字典的内容将刷新到状态提供者。

对我来说,这听起来像是说我的轮询请求线程确实在请求结束时将其整个会话序列化回状态服务器,即使它没有&# 39;做了任何改变。另外,我可以理所当然地说,我的后台线程写入的会话字典在我修改之后从未被序列化回状态服务器,因为它的请求已经结束。谁能证实这一点?

1 个答案:

答案 0 :(得分:0)

我在此页面上发现了一些可能相关的信息:

  

当页面将数据保存到Session时,该值将加载到a   由HttpSessionState类托管的定制词典类。   字典的内容在刷新时会刷新到状态提供者   正在进行的请求完成。

对我而言,这听起来像是说我的轮询请求线程确实将其整个会话在请求结束时序列化回状态服务器,即使它没有做出任何更改。另外,我可以理所当然地认为,我的后台线程写入的会话字典在我修改之后永远不会被序列化回状态服务器,因为它的请求已经结束了。

我把上面的证据作为确认。我将状态存储在数据库而不是会话中。