Zoltan Kochan
Zoltan Kochan

Reputation: 7636

Initializing culture in an asynchronous action complete

I am using ASP.NET MVC 3. Once I've decided to change some of my synchronous actions to asynchronous ones. Before doing that I created a sample asynchronous action to test whether it will work properly or not. It was something like this:

    public void SampleAsync()
    {
        AsyncManager.OutstandingOperations.Increment();
        var task = System.Threading.Tasks.Task.Factory.StartNew(() => DoStuff());
        task.ContinueWith(t =>
        {
            AsyncManager.Parameters["pars"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });
    }

    private object DoStuff()
    {
        System.Threading.Thread.Sleep(20000);
        return null;
    }

    public ActionResult SampleCompleted(object pars)
    {
        return View();
    }

But this action didn't work asynchronously! It was absolutely synchronous. At least, it was blocking other requests (while running) as a traditional action. After a few hours of investigation I've found out that the problem was in my asax where I was initializing the current culture:

    protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        CultureInfo ci;
        //It's important to check whether session object is ready
        if (HttpContext.Current.Session != null)
        {
            ci = (CultureInfo)this.Session["Culture"];
            //Checking first if there is no value in session 
            //and set default language 
            //this can happen for first user's request

            if (ci == null)
            {
                ci = GetCultureFromCookie();
                this.Session["Culture"] = ci;
            }
            if (ci == null)
            {
                ci = GetStandardCulture();
                this.Session["Culture"] = ci;
            }
        }
        else
        {
            ci = GetCultureFromCookie();
            if (ci == null) ci = GetStandardCulture();
        }
        //Finally setting culture for each request
        Thread.CurrentThread.CurrentUICulture = ci;
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
    }

When I commented out this method, everything started to work just fine. I assume that the problem is related with accessing the HttpContext.Current from an action complete thread. But in this case, how should I init the current culture of the user?

Upvotes: 1

Views: 550

Answers (1)

Darin Dimitrov
Darin Dimitrov

Reputation: 1038850

The following line is the problem:

this.Session["Culture"] = ci;

You are writing to the session. In ASP.NET the session is not thread safe. For this reason ASP.NET synchronizes access to it. This basically means that if you hit an ASP.NET handler that does writes to the session from 2 parallel requests from the same session, ASP.NET will simply execute them sequentially.

Don't get me wrong. This doesn't mean that two parallel requests from 2 different users will be executed sequentially. They will be perfectly parallel. It's just that I suppose you ran 2 parallel AJAX requests to this controller action and you observed the behavior in FireBug or whatever. Since the 2 AJAX requests were from the same session and since you write to the session, you are busted, your requests cannot be executed in parallel.

You will have to find another way to handle culture. Cookies or url parameters (better in terms of SEO) spring to mind as a nice alternative. And since sessions are bad thing anyways, getting rid of them would only be beneficial to your application.

In ASP.NET MVC 3 you could use the SessionState attribute to indicate that a controller action will either not use session or it will be only reading from it:

[SessionState(SessionStateBehavior.ReadOnly)]
public class FooController: AsyncController
{

}

Now you can have parallel processing of requests from the same session. Obviously every attempt to write to the session will throw an exception.

Upvotes: 2

Related Questions