56

I have a C# ASP.NET application which starts about 25 different threads running some methods in a class called SiteCrawler.cs.

In HttpContext.Current.Session I want to save the result of the search made by the user and present it to the user when all the threads are finished running. My problem is that the HttpContext.Current object is null in the spawned threads because it doesn't exist there.

What other options do I have to save user/session specific data without using session because of the limitations when the application is multithreaded?

I have tried to search around every inch of Stackoverflow to find a solution but without any luck....

ProNotion
  • 3,478
  • 2
  • 19
  • 27
Raydk
  • 579
  • 1
  • 4
  • 9
  • 1
    I guess you could always pass the current HttpContext when you create the new threads and update the session in that one – musefan Jan 19 '12 at 11:30
  • Isn't it possible to run the 25 threads, collect the results in the asp.net thread and then store the complete results in your session? – Wouter de Kort Jan 19 '12 at 11:31
  • 4
    Spawning threads for 'long running processes' is, by rule of thumb, a bad idea in a web-facing application. – Grant Thomas Jan 19 '12 at 11:36
  • 1
    musefan - I thought about that, but i'm not sure if it's a good idea, because the spawned thread would change the content of the passed on HttpContext which by the time also would be different. – Raydk Jan 19 '12 at 12:14
  • Wouter de Kort - How would your approach be to achieve this? – Raydk Jan 19 '12 at 12:15
  • I've been looking to solve a related problem, and it seems like the "right" way to flow the `HttpContext` is to get your hands dirty with the `SynchronizationContext`. Rather than going into detail here, I'll just point you to this [thorough and well-written blog post](https://blogs.msdn.microsoft.com/friis/2017/12/02/asp-net-httpcontext-in-asyncawait-patterns-using-the-task-parallel-library-part-1/) which address your question about `HttpContext`, and in more complex threading scenarios too. In it he mentions this classic post by Stephen Toub, [ExecutionContext vs SynchronizationContext](http – Mark Feb 28 '19 at 18:46
  • @GrantThomas Serious question here: Chrome has a (far too short) timeout on queries that we have been unable to control (it's actually very inconsistent). How then would you suggest structuring a call to a long-running query? – David I. McIntosh Dec 23 '19 at 17:08

5 Answers5

82

In my application there are a lot of code that uses HttpContext.Current and I can not modify that code.

worker.DoWork() from sample below uses that code. And I had to run it in separate thread.

I came to the following solution:

 HttpContext ctx = HttpContext.Current;
 Thread t = new Thread(new ThreadStart(() =>
                {
                    HttpContext.Current = ctx;
                    worker.DoWork();
                }));
 t.Start();
 // [... do other job ...]
 t.Join();
Dmitry Andrievsky
  • 1,623
  • 15
  • 18
  • wow thanks you saved me! i wonder how this hits that memory if i use it more – CMS Nov 01 '15 at 08:23
  • 10
    Note that in general this is a bad thing to do. For example, sometimes you may find that HttpContext.Current.Items[x] returns null instead of the value you put there, because ASP has cleaned up resources after the http request has finished. This is a quick & dirty approach that probably will work most of the time but don't rely on it for anything important. – Rory Dec 30 '15 at 17:03
  • 3
    I disagree with @Rory 's point. I think its the most elegant solution for this by far when you compare it with other options. First of all you're not cloning HttpContext but instead just passing a reference. As Thread may run longer than HttpRequest itself one could easily check if HttpContext is null prior to accessing it. As an improvement to above code snippet passing "ctx" could also be done via WeakReference - this way the thread won't prevent HttpContext from being GC'ed if needed. Again depending on the life-cycle you need you adjust the code above to fit your needs. + Accepted Answer – AlexVPerl Feb 02 '16 at 22:33
  • 1
    @Rory that implies a fire-and-forget async call, and *that* is a bad idea. If you don't do anything funky and make sure that the web request waits for all async calls to finish, you should be fine, right? – user247702 Jul 05 '16 at 08:17
  • 2
    I think I hadn't noticed the `t.Join()` call on this answer when I wrote my previous comment. I agree it _should_ be fine if the background threads finish before the http request. In my experience the problems have occurred when the web request finishes first and the background task is still running. Note the problem is NOT that the HttpContext gets gc-ed - clearly there's still a reference so it won't be - but that something in ASP.NET deletes all the elements in `HttpContext.Current`, so then if you expect anything to be in that collection you're out of luck. – Rory Jul 05 '16 at 11:50
  • @MinhNguyen did you need to access the `HttpContext.Current.Session` in your case? Using this approach, I can receive the `HttpContext.Current` but not the `HttpContext.Current.Session` – Don Cheadle Jul 21 '16 at 16:35
  • helped me to use parallel library in asp.net application with session. – Dinesh Kumar Jan 23 '18 at 14:47
  • How can I set `HttpContext.Current` if I am injecting `HttpContextBase` into my class? – Vin Shahrdar Jun 11 '18 at 20:38
  • I faced a similar restriction and used FakeHttpContext which is serialized (I capture all session variables) and then restored into this FakeHttpContext (like [this one](https://stackoverflow.com/questions/30909943/how-to-setup-request-header-in-fakehttpcontext-for-unit-testing) – drizin Nov 24 '19 at 20:13
11

Have a look at this article by Fritz Onion: Use Threads and Build Asynchronous Handlers in Your Server-Side Web Code. It's quite long, but your requirement is not too trivial.

Also K. Scott Allen posted a somewhat shorter article about this very issue: Working With HttpContext.Current

Duncan Smart
  • 27,805
  • 8
  • 60
  • 69
Dennis Traub
  • 46,924
  • 7
  • 81
  • 102
  • 1
    That article 'Working with HttpContext.Current' was vital in assisting me with my particular gotcha, thanks. – mungflesh Sep 07 '15 at 09:51
7

@Rory made a comment above, that certain objects in the HttpContext will become null even if you pass it into the Thread. This happened to me with the User property. So instead you can copy the User to the thread CurrentPrincipal like this:

In the controller context, save off the user:

            _user = HttpContext.Current.User;
            var processThread = new Thread(() => ThreadedCode());
            processThread.Start();

In the thread, set the 'Thread's' user:

    private static void ThreadedCode()
    {
        // Workaround for HttpContext.Current.User being null.
        // Needed for CreatedBy and RevisedBy.
        Thread.CurrentPrincipal = _user;

Note that the HttpContext will only be available for the lifetime of the request. The thread will live on potentially much longer than the request, which is probably why you need a thread in the first place! :)

Community
  • 1
  • 1
Jess
  • 20,424
  • 18
  • 108
  • 130
  • 1
    @Rory thanks for your comment above. It got me thinking about this alternate answer. – Jess Feb 23 '16 at 22:05
4

Just add the HttpContext.Current to the constructor of your class SiteCrawler.cs

public class SiteCrawler
{
     HttpContext context = HttpContext.Current;

    public void Method()
    {
        context.WhateverYouWant
    }
}
jjspierx
  • 251
  • 2
  • 13
3

You can save it to the database and then, you can let the user's browser to keep refreshing or using ajax or using the new signalr to check if the result is already written in db. hope it helps.

ysrb
  • 6,563
  • 2
  • 27
  • 29
  • Saving the session variables to a database wouldn't be a problem. I just don't know what the best way would be to bind it to the correct session and retrieve it when the threads are done. – Raydk Jan 19 '12 at 12:18