SnickersAreMyFave
SnickersAreMyFave

Reputation: 5143

C#: Adding context to Parallel.ForEach() in ASP.NET

I have a static class with a static get property, and in this property, I do this:

// property body
{
    // HttpContext.Current is NOT null
    ...

    Parallel.ForEach(files, file =>
    {
        // HttpContext.Current is null
        var promo = new Promotion();
        ...
    });
    ...

    // HttpContext.Current is NOT null
}

This static class doesn't undergone type initialization until a view uses this property.

The problem is that Promotion's static constructor, which is initialized the first time a new Promotion() is created within the Parallel.ForEach(), uses HttpContext.Current. When promo is instantiated within the scope of this Parallel.ForEach(), HttpContext.Current is null, and new Promotion() therefore causes an exception.

HttpContext.Current is not null within the static get property because it's not called until the view uses it (and there is therefore a HttpContext.Current).

If Promotion used HttpContext.Current in its instances instead of its static members, I could probably just pass HttpContext.Current into the new Promotion() constructor:

 var context = HttpContext.Current;
 Parallel.ForEach(files, file =>
 {
     var promo = new Promotion(context);
 });

But since static members of Promotion need HttpContext.Current, I can't. I could probably redesign the Promotion class to change the static members that need it to be instance members, but they are static for a reason--there would be a large performance penalty if all the members that were static had to be defined instead on each instance each time a new Promotion was instantiated.

What are the possible workarounds for this? I didn't realize HttpContext.Current would be null within the scope of Parallel.ForEach().

Upvotes: 13

Views: 8917

Answers (4)

germensinho
germensinho

Reputation: 1

It does not work, because inside the foreach, a new thread is created, so the context is null. Even creating a method DoSomething to set the curren context, the context is still null.

Upvotes: 0

mmmeff
mmmeff

Reputation: 1033

Just pass any context you have from outside the Parallel.ForEach call down into any functions you call inside that rely on said context.

var context = HttpContext.Current;
Parallel.ForEach(items, item =>
    {
        DoSomething(item, context);
    }
);



private static void DoSomething(item, context = null) {
    if (context == null) context = HttpContext.Current;

    ...
}

I like to have fallback to null so I don't have to worry about passing around the context all the time. I only make sure to remember that my functions need context when calling from another thread, and then I slap that baby right in there.

Upvotes: 4

user180326
user180326

Reputation:

As Mauricio points out, the HttpContext.Current depends on the currently executing thread. It strikes me as unusual that a static constructor depends on the such an inherently transient value as HttpContext.Current, but perhaps that was not your idea.

If you can change the Promotion class, that would be the first option I'd consider.

If not, you need somehow to force type initialization for Promotion at a point where HttpContext.Current is still valid. To learn what forces type initialization read this Jon Skeet blog post.

An option could be to create a dummy Promotion object, (just once in the entire program should be enough). If that is not an option, you could try by reading the property with reflection. I don't know if that forces type initialization, but I guess so.

Upvotes: 0

Mauricio Scheffer
Mauricio Scheffer

Reputation: 99720

HttpContext.Current is null because it's running in "non-web threads". If you forked some code using new Thread(...) it would be exactly the same. The TPL somewhat hides this, but you still need to realize that each iteration in your Parallel.ForEach can potentially run in a different thread, and treat it accordingly.

In particular, if you want to use some class or method out of the web request (and Parallel.ForEach is such an usage) you just can't use HttpContext.Current. A workaround is to explicitly pass the HttpContext (or HttpContextBase for improved testability) in the constructor (or as a method parameter)

In a nutshell: you need to break out of using HttpContext.Current statically.

Upvotes: 11

Related Questions