Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET Multithreading in a Web Farm Environment

0.00/5 (No votes)
2 Aug 2009 1  
ASP.NET Multithreading in a web farm environment

In one of my recent projects, I have a situation where I need to generate two files on the server based on user input. Both files can be quite big (in terms of mobile devices), and take a long time to generate by the server and to download by the user. The two files must be generated sequentially, as the second file depends on the first. One way to implement this would be to generate both files and then return to the user the download URLs in one request-response. The problem with this approach is that the user has to wait for a very long time before the server finishes the job. An obvious improvement is to return immediately after the first file is generated, and at the same time to spawn a new thread to generate the second file. This way, upon the request responded, the user can start to download the first file while the server is working on the generation of the second file. One problem with this approach is that the user will not know when the second file is ready for download. My solution is to create another page that allows the user to check the status of the second file. If it is ready, the response will include the download URL so the user can start to download it. If it is not ready, the user can either wait or do something else and check the status later. This works fine in my case, as the downloading time for the first file is almost always longer than the time needed to generate the second file.

Another problem with this approach is that my ASP.NET application is running in a web farm environment in which I have a shared file store for the generated files and the newly spawned thread does not have proper permissions to write to the shared file store. The following diagram shows my simple web farm of two web servers, WS1 and WS2, and the file store. The file store is just a shared folder on one of the two web servers.

I use the steps below to configure the web servers so they can access to the shared folder. These steps apply to Windows 2000 (Advanced) Server OS. Yes! I'm still using Windows 2000 servers. :)

  1. Create a Windows user account, say "SharedUsr", on both WS1 and WS2.
  2. Add <identity impersonate="true" userName="SharedUsr" password="xxxxxx"/> to web.config on both servers.
  3. Run these commands on both servers to grant "SharedUsr" access to the shared folder used by my ASP.NET application:
    C:\WINNT\Microsoft.NET\Framework\v2.0.50727>aspnet_regiis -ga WS1\SharedUsr
    C:\WINNT\Microsoft.NET\Framework\v2.0.50727>aspnet_regiis -ga WS2\SharedUsr
  4. Make sure "SharedUsr" has write permission on the shared folder

This, however, does not work if the current thread spawns a new thread to write to the shared folder, because the new thread does not automatically impersonate "SharedUsr". I have to write code to force the impersonation. The following is the code. I have left out some details for clarity.

......
using System.IO;
using System.Security.Principal;
using System.Threading;

public partial class GenerateFiles : System.Web.UI.Page
{
    private WindowsIdentity wid = null; 
    protected void Page_Load(object sender, EventArgs e)
    { 
        //Generate file1 ......

        wid = WindowsIdentity.GetCurrent(); //This is "SharedUsr" 
        try
        {
            //Use a thread from the threadpool to generate file2
            if (ThreadPool.QueueUserWorkItem(new WaitCallback(File2Callback),
                new File2Generator(file2Location)))
            {
                //Return file1 URL to user so he can start to download file1
                Response.Write(file1URL); 
            }
            else
            {
                Response.Write("Server too busy. The work item could not be queued.");
            }
        }
        catch (Exception ex)
        {
            Response.Write(ex.Message);
        }  
    }

    private void File2Callback(object o)
    {
        //Set the pooled worker thread's principal to that of wid   
        WindowsPrincipal principal = new WindowsPrincipal(wid);
        Thread.CurrentPrincipal = principal;

        //Make the pooled worker thread to impersonate 'SharedUsr'
        WindowsImpersonationContext wic = wid.Impersonate();

        //Generate file2
        File2Generator f2gen = (File2Generator)o;
        f2gen.Generate();

        //Undo impersonation before return to threadpool
        wic.Undo();
    }
}

In the beginning of Page_Load, we assume file1 has been generated. Then, we use a worker thread from the threadpool to queue the generation of file2 for execution. The QueueUserWorkItem method has two parameters. The first parameter specifies the function to be called when the worker thread has its turn. The second parameter is the data to be passed to the function. In our case, it's an instance of File2Generator class, which encompasses necessary data and functionality to generate file2. The implementation of File2Generator is not listed here because the detail is not important for making the point.

The callback function, File2Callback, will be executed on the worker thread that has the Windows identity "ASPNET", not "SharedUsr". So in it, we need to change the worker thread's impersonation before calling the Generate method of File2Generator.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here