15

我这里有个问题。在我的网络应用程序中,我有一个页面启动另一个线程来执行耗时的任务。在这个新线程中,我调用了我的一种架构方法(在另一个项目中 - 一个架构项目)。问题是:在其中一种方法中,我访问了HttpContext.Current.Session字段。但是当我启动应用程序时,会抛出一个异常,说这个对象(HttpContext.Current.Session)有一个空引用。我如何将新线程的上下文设置为与 HttpApplication 上下文相同以访问HttpContext.Current.Session

4

3 回答 3

12

这里有很多事情需要考虑。

如果您的线程的生命周期与页面的生命周期相同,并且您需要大量随机访问HttpSessionState,那么您应该从使用静态属性SynchronizationContext创建后台线程的调用中获取。Current

一旦你有了它,你可以将它传递给你的线程,然后当你需要访问与请求相关联的任何东西HttpContextBase(这包括会话)时,你可以调用你传递给线程的Post方法来获取值SynchronizationContext(或设置它们):

// From thread servicing request.
var sc = SynchronizationContext.Current;

// Run the task
Task t = Task.Run(() => {
    // Do other stuff.
    // ...

    // The value to get from the session.
    string sessionValue = null;

    // Need to get something from the session?
    sc.Post(() => {
        // Get the value.
        sessionValue = HttpContext.Current.Session["sessionValue"];
    }

    // Do other stuff.
    // ...
});

这样做很重要,因为访问HttpContextBase(以及与之相关的任何内容)不是线程安全的,并且与处理请求的线程(嗯,上下文)相关联。

请注意,该Post方法不会阻塞,因此调用之后的代码Post(即 之后的行// Do other stuff.)应该独立于传递给的委托Post。如果后面的代码是依赖的,需要等待调用完成再继续,那么可以调用Send方法;它具有相同的签名,并且会一直阻塞,直到委托中的代码被执行。

也就是说,如果您只想对值进行只读访问,那么最好在调用代码之前获取它们,然后在后台线程中访问它们:

// Get the values needed in the background thread here.
var values = {
    SessionValue = HttpContext.Current.Session["sessionValue"];
};

// Run the task
Task t = Task.Run(() => {
    // Do other stuff.
    // ...

    // Work with the session value.
    if (values.SessionValue == ...)

    // Do other stuff.
    // ...
});

如果你的线程在请求​​被处理后还要继续处理,那么你只有只读状态,你必须在启动线程之前捕获它。一旦请求得到服务,即使会话存在,它也是一个逻辑概念;根据会话状态的提供者(会话状态管理器、SQL Server 等),每次有新请求进来时,对象都可能会被水合。

您还必须处理会话超时问题,您甚至不知道会话是否存在于您想要访问它的点。

于 2012-10-23T18:54:26.340 回答
3

如果将当前上下文传递给子线程,问题在于它依赖于父上下文。如果父上下文死了,那么你的线程将无法再访问上下文并且它会导致问题。

一种解决方案是克隆父上下文,然后在线程中使用克隆。这样,如果父线程处置,该线程将继续工作并可以访问所有上下文好东西。

HttpContext ctx = ThreadingFixHttpContext();
Thread newThread = new System.Threading.Thread(new ThreadStart(() =>
{
    HttpContext.Current = ctx;
    Thread.CurrentPrincipal = ctx.User;
    var test = HttpContext.Current.Session["testKey"];
}));
newThread.Start();

这也应该适用于 Task.Run() 方式。完成这项工作的方法:

    private static Object CloneObject(Object Source)
    {
        MemoryStream Stream = new MemoryStream();
        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter Formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
        Formatter.Serialize(Stream, Source);
        Stream.Position = 0;
        object Clone = (object)Formatter.Deserialize(Stream);
        Stream.Close(); Stream.Dispose();
        return Clone;
    }

    public static System.Web.HttpContext ThreadingFixHttpContext()
    {
        //If this method is called from a new thread there is issues holding httpContext.current (which is injected from parent thread in AniReturnedPaymentsFetch.ascx.cs
        //The parent http current will die of its own accord (because it is from a different thread)
        //So we clone it into thread current principal. 
        System.Security.Principal.WindowsIdentity ThreadIdentity =
            (System.Security.Principal.WindowsIdentity)CloneObject(System.Web.HttpContext.Current.User.Identity);

        //Then create a new httpcontext using the parent's request & response, so now the http current belongs to this thread and will not die.
        var request = System.Web.HttpContext.Current.Request;
        var response = System.Web.HttpContext.Current.Response;
        var ctx = new System.Web.HttpContext(request, response);
        ctx.User = new System.Security.Principal.WindowsPrincipal(ThreadIdentity);
        return ctx;
    }
于 2017-11-27T10:12:17.487 回答
1

您无法从线程中访问会话,但您可以使用以下方式共享数据:HttpRuntime.Cache

不过有几件事要记住:与会话不同,缓存确实会过期。此外,缓存在所有 Web 用户之间共享。

于 2014-01-07T20:02:38.463 回答