Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access session data from another thread

Tags:

c#

.net

asp.net

I have a issue here. In my web app i have a page that starts another thread for time consuming task. In this new thread i have a call to one of my architecture methods (Inside another project - an architecture project). The problem is: in one of this methods i access a HttpContext.Current.Session field. But when i launch the application a exception is thrown saying that this object (HttpContext.Current.Session) has a null reference. How could i set the context of the new thread the same as HttpApplication context in order to access HttpContext.Current.Session ?

like image 390
mnatan.brito Avatar asked Oct 23 '12 18:10

mnatan.brito


2 Answers

There's a number of things to consider here.

If your thread has a lifetime equal to that of the page and you need a good deal of random access to the HttpSessionState, then you should get the SynchronizationContext from the call that creates the background thread using the static Current property.

Once you have that, you can pass that to your thread and then when you need access to anything on the HttpContextBase associated with the request (and this includes the session), you can call the Post method on the SynchronizationContext that you passed to your thread to get values (or set them):

// 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.
    // ...
});

It's important to do this, as access to the HttpContextBase (and anything associated with it) is not thread-safe and is tied to the thread (well, the context) processing the request.

Note that the Post method doesn't block, so code that comes after the call to Post (i.e. the lines after // Do other stuff.) should be independent of the delegate that's passed to Post. If the code that comes after is dependent and you need to wait for the call to complete before continuing, then you can call the Send method; it has the same signature and will block until the code in the delegate is executed.

That said, if you want just read-only access to the values, then it's better to get them before you call your code, and then access them in your background thread:

// 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.
    // ...
});

If your thread is going to continue to process after the request is serviced, then you only have read-only state, and you have to capture it before you start the thread. Once the request is serviced, even though the session lives, it's a logical concept; depending on the provider for the session state (session state manager, SQL Server, etc.), the object could be hydrated every time a new request comes in.

You'd also have to deal with session timeout issues, you wouldn't know if the session even exists at the point you want to access it.

like image 68
casperOne Avatar answered Oct 19 '22 22:10

casperOne


If you pass the current context into the child thread, the problem is that it is dependent on the parent context. If the parent context dies, then your thread will no longer have access to the context & it will cause problems.

One solution is to clone the parent context, then use the clone in the thread. That way, if the parent thread disposes, the thread will continue to work & have access to all the context goodies.

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();

This should also work with the Task.Run() way too. The methods that do the work:

    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;
    }
like image 43
ShrapNull Avatar answered Oct 19 '22 21:10

ShrapNull