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

Modifying session from background thread in StateServer mode

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

我已经附加了调试器,我观察到的是后台线程在会话中设置 BackgroundTaskStatusStatus 属性,但是当后续状态轮询时requests 从会话中读取该对象,属性 值不变。他们似乎在会话对象的两个不同副本上进行操作。

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

但是,似乎会话在某个时候被分支并且正在存储我的对象的两个不同的序列化副本,或者后台线程设置的 Status 正在被覆盖,即使 GetStatus() 不写入会话。哪里出错了?

此外,像我正在做的那样传入 HttpSessionState 对象是否安全,或者它是否可以在后台线程完成之前被销毁(即它的范围是否限于初始请求)?我的印象是它是一个静态物体,但现在我对此表示怀疑。我希望这对农场 运行 是安全的,但我希望不必涉及数据库。

编辑

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

When a page saves data to Session, the value is loaded into a made-to-measure dictionary class hosted by the HttpSessionState class. The contents of the dictionary is flushed to the state provider when the ongoing request completes.

对我来说,这听起来像是在说我的轮询请求线程确实在其请求结束时将其整个会话序列化回状态服务器,即使它还没有' 做了任何改变。此外,有理由认为,我的后台线程写入的会话字典在我修改它之后永远不会序列化回状态服务器,因为它的请求已经结束。谁能证实这一点?

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

When a page saves data to Session, the value is loaded into a made-to-measure dictionary class hosted by the HttpSessionState class. The contents of the dictionary is flushed to the state provider when the ongoing request completes.

对我来说,这听起来像是在说我的轮询请求线程确实在其请求结束时将其整个会话序列化回状态服务器,即使它没有进行任何更改。此外,有理由认为,我的后台线程写入的会话字典在我修改后永远不会序列化回状态服务器,因为它的请求已经结束。

我以上面的证据为证。我求助于将状态存储在数据库中而不是会话中。