Geeks With Blogs
Kalyan Krishna Kalyan's technology blog

While working on an ASP.net application, we had a certain page where we wanted to conduct a search that takes about 30-45 seconds to complete.

To desist from holding the page output for all that while this search took place, we decided to make it look more interactive for the user using some nifty AJAX.

The following are the steps we took..

  1. Started the search on page load on a separate thread using BeginInvoke()
  2. The IAsyncResult returned by BeginInvoke() was saved in a session variable.
  3. An AJAX timer was placed on the page that allowed us to periodically query the status of the search.
  4. The query method would fetch the IAsyncResult instance from the session variable and query its IsCompleted property.
  5. When IsCompleted was true, we’d send back the results to the page and switch off the timer.

 

This all worked very well in our development environment where the session state is maintained InProc.

 

When we deployed our application on our production environment, we were in for a shock.  The production environment uses SqlState (OutProc) . In an out of process session state, only serializable types can be stored in session variables.  It turned out that IASyncResult is *not* serializable and we had to roll back the code.

 

We did learn a lot many lessons from this, and we now test our ASP.Net application in SqlState before deploying them on production sites, but apart from this I decided to look for some ideas as how one can provide such interactive behavior in ASP.net environment.

 

The solution is a bit complicated, but not so difficult to achieve either. The steps are being outlined below.

  1. You need to move your code that executed on separate threads in an out of process application. Using a remoting server is the easiest option.
  2. The code that you’d execute on a separate thread in ASP.net environment cannot access session variables. A worker thread must not touch objects owned by the main thread. The session object is owned by the main thread. There are ways around this, but all are messy and should be avoided.
  3. The best and elegant solution is to pass all data inputs as parameters to methods executing on separate  threads.
  4. Anyways, since our code is non-session dependent and in a separate assembly (dll), we were able to expose the assembly methods through a remoting server.
  5. Now the ASP.Net page, for each timer event, queries an out of process application for the value of IAsyncResult.IsCompleted and works in SqlState production environment without any hassles.

 

I am adding some more code to help those who want to set up a remoting server themselves and get started with the above mentioned approach

 

1.      Set up a basic remoting server. There are literally tens of samples available online if you google it .  One of these sample article to set up a remoting server can be found at the following Url
http://www.codeproject.com/KB/IP/dotnetremotingbasictutor.aspx
http://www.csharphelp.com/archives2/archive314.html

2.      Take care of the following when setting up a remoting server.

a.      For SAO (server activated object),  read well about the two available modes, Singleton and Singlecall. If you are to query a running thread between calls , I recommend Singleton mode for a wrapper class, say named MyWrapperClass. The MyWrapperClass class runs in Singleton mode and manages running threads and the shared data cache used by the various threads..

public class MyWrapperClass : System.MarshalByRefObject

b.      Do a good deal of reading as to which of the available channels, TcpChannel and HttpChannel is suitable for your case.

c.      Be default, the remoting server would kill your Singleton object (in our case the MyWrapperClass class) when the wrapper does not receives a request for some time. You might want to avoid this as it would kill your cache .  To control the amount of time you want the Singleton to live, remoting services provides something called a Lease. For infinite lease, use the following code.

public override object InitializeLifetimeService()
{
    return null; //Infinite Lease
}

 

3.      Whenever a call is made on one of the remoting server’s exposed Wrapper class’s methods, I’d recommend the following steps..

a.      Create and initialize hash table, named say myExecutingInstances.

private static Hashtable myExecutingInstances = new Hashtable();

b.      Create an object of the class , say named MyMultithreadedClass, that has the multi-threaded method calls. DO NOT put multi-threading code in the MyWrapperClass itself as things would get very messy.

public class MyMultithreadedClass

c.      Declare a public method in the MyWrapperClass that clients would execute, like

                                                                        i.     public string StartExecuting(string someparams)

d.      When the above method (MyWrapperClass.StartExecuting () is called, execute the method that does multi-threaded processing on this instance of  MyMultithreadedMethodsClass., e.g. MyMultithreadedMethodsClass. StartExecuting (). Make sure that this method is NON-BLOCKING or you’d end up holding up all further processing..

public void StartExecuting(string someparams) //THIS HAS TO BE NON-BLOCKING

e.      Generate a unique guid as key and , save this key and the newly created instance of MyMultithreadedMethodsClass in the hashtable myExecutingInstances.

public string StartExecuting(string someparams)
{

 Guid newguid = Guid.NewGuid();

 MyMultithreadedMethodsClass newobj = new     MyMultithreadedMethodsClass();

newobj.StartExecuting(someparams);  //This should execute in non-blocking manner or the code would stuck here....

 myExecutingInstances.Add(newguid, newobj);    
 return newguid;
}

f.       Return this guid to the caller.

g.      Provide a method in your MyMultithreadedMethodsClass class to check for completion of the task or query progress, like

                                                                        i.     public bool IsExecutionComplete()

                                                                       ii.     public int NumRecordsFetched();

4.      To check for the progress, declare a similar method (s) in the MyWrapperClass class, which in turn calls the similarly named methods  in MyMultithreadedMethodsClass..

a.      public bool IsExecutionComplete(string guid)

b.      public int NumRecordsFetched(string guid)

 

5.      The above MyWrapperClass methods would first hit the myExecutingInstances hash table to fetch the running instance of MyMultithreadedMethodsClass matching the guid. Once the MyMultithreadedMethodsClass instance is found,  put code to call the needed methods on the MyMultithreadedMethodsClass instance.

MyMultithreadedMethodsClass newobj = (MyMultithreadedMethodsClass)myExecutingInstances[guid];

6.      Provide another method in the MyWrapperClass, and the actual implementation class MyMultithreadedMethodsClass, to return the processed data when done. Again they call  the similarly named methods on MyMultithreadedMethodsClass.

a.      Public Dataset MyWrapperClass.GetResults(string  myguid);

b.      Public Dataset MyMultithreadedMethodsClass.GetResults();

7.       Finally, to ensure that the hashtable myExecutingInstances is not choked up with all the instances being added to it, provide a facility to clean up when processing is done and results fetched by MyMultithreadedMethodsClass.

a.      This would set the MyMultithreadedMethodsClass instance in the hastable to null, and then remove it from the hashtable altogether.

public void ClearHashtable(string guid)
{
     myExecutingInstances[guid] = null;
  myExecutingInstances.Remove(guid);
}

 

The complete sample code (NOTE- Its only for reference, don’t try to complie it..)

 

class MyListener

    {

        static void Main(string[] args)

        {

            TcpChannel listenerchannel = new TcpChannel(2099);

            ChannelServices.RegisterChannel(listenerchannel, false);

           

            WellKnownServiceTypeEntry WKSTE = new WellKnownServiceTypeEntry(typeof(MyWrapperClass), "MyRemotingListener", WellKnownObjectMode.Singleton);

            RemotingConfiguration.RegisterWellKnownServiceType(WKSTE);

        }

    }

 

 

public class MyWrapperClass : System.MarshalByRefObject

    {

        private static Hashtable myExecutingInstances = new Hashtable();

 

 

        public override object InitializeLifetimeService()

        {

            return null; //Infinite Lease

        }

 

        public string StartExecuting(string someparams)

        {

            Guid newguid = Guid.NewGuid();

            MyMultithreadedMethodsClass newobj = new MyMultithreadedMethodsClass();

            newobj.StartExecuting(someparams);  //This should execute in non-blocking manner or the code would stuck here....

            myExecutingInstances.Add(newguid, newobj);

 

            return newguid;

        }

 

        public bool IsExecutionComplete(string guid)

        {

            MyMultithreadedMethodsClass newobj = (MyMultithreadedMethodsClass)myExecutingInstances[guid];

            return newobj.IsExecutionComplete();

        }

 

        public int NumRecordsFetched(string guid)

        {

            MyMultithreadedMethodsClass newobj = (MyMultithreadedMethodsClass)myExecutingInstances[guid];

            return newobj.NumRecordsFetched();

        }

 

        public DataSet GetResults(string guid)

        {

            MyMultithreadedMethodsClass newobj = (MyMultithreadedMethodsClass)myExecutingInstances[guid];

            return newobj.GetResults();

        }

 

        public void ClearHashtable(string guid)

        {

            myExecutingInstances[guid] = null;

            myExecutingInstances.Remove(guid);

        }

    }

 

 

 

public class MyMultithreadedMethodsClass

    {

        public void StartExecuting(string someparams) //THIS HAS TO BE NON-BLOCKING

        {

 

        }

 

        public bool IsExecutionComplete()

        {

 

        }

 

        public int NumRecordsFetched()

        {

 

        }

 

        public DataSet GetResults()

        {

 

        }

 

    }

 



 

Posted on Friday, May 16, 2008 10:56 AM | Back to top


Comments on this post: Beware of saving IAsyncResult in a session variable

# re: Beware of saving IAsyncResult in a session variable
Requesting Gravatar...
You mentioned that the process takes 45 sec. So, if you use another within the AppDomain, wouldn't you out of threads considering ASP.Net has limited numbers of thread?
Also, have you tried to see how this performs under load? Any benchmark? It'd be interesting to see.
Left by Rachit on May 17, 2008 5:53 AM

# re: Beware of saving IAsyncResult in a session variable
Requesting Gravatar...
Using a out of process application, like a remoting server removes the thread contention problem really good.

And I am not making this up!

Using a multi-threaded application in an ASP.NET application which is already under a huge load is a recipe for disaster.

ASP.NET already has its own thread pool to serve requests and a multi-threaded approach would only lead to more thread starvation.

So, using a out of process application (different appdomain) solves this problem of my multi-threaded application not exhausting the threads from ASP.NET thread pool.

Also, Remoting servers are easily scalable to multiple servers in a web farm like environment (NLB). So if you have an application running under a remoting interface that is heavily multi-threaded, in a NLB environment, it will easily serve any high performance site very well..

We actually already have a website (NLB of 4 servers) and a multi-threaded application which extremely hungry for threads working under a remoting server. The NLB for the remoting server is spread over 14 servers.

Adding a bit of nifty timeouts to ensure that threads don't hang during processing, we have been successfully processing over a million requests a day without any major problems.

Hope this clears any doubts..
Left by Kalyan Krishna on May 18, 2008 1:00 AM

# re: Beware of saving IAsyncResult in a session variable
Requesting Gravatar...
"And I am not making this up!"

In my response above, I never mentioned that you are faking this up.

"Using a multi-threaded application in an ASP.NET application which is already under a huge load is a recipe for disaster."

Yes, that's the reason I mention "within" AppDomain & multitheading is no good.

In short, I'm not doubting the information in this post, I was just wondering if you had any numbers so that the readers can understand better by comparison.

If you have any sample code that you can zip it up and attach, would be a great resource.
Left by Rachit on May 18, 2008 6:13 AM

# re: Beware of saving IAsyncResult in a session variable
Requesting Gravatar...
"And I am not making this up!" was more of an exclamation from excitement than a doubt. Sorry if you misunderstood it.

I'd try to post some sample code ASAP.

As for the numbers, its more dependent on the kind of processing that a method on a thread does, so I can only give you some estimates here as why our system needs 14 servers !

Our multi-threaded applications on remoting makes calls to various web services and aggregate data.
Also the processing is mostly in-memory and requires large quantities of RAM being made available.
We started increasing the servers when we realized that a single servers would scale to at maximum of 800 or so simultaneously executing threads (making web service calls)

I'd say a more *normal* not-so-memory intensive application would easily do quiet well with 2 servers.
Left by Kalyan Krishna on May 19, 2008 1:12 AM

Your comment:
 (will show your gravatar)


Copyright © Kalyan Krishna | Powered by: GeeksWithBlogs.net