Friday, May 16, 2008

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()

        {

 

        }

 

    }

 



 

Friday, March 14, 2008

Working with multiple threads is never easy and every now and then I keep running into seemingly bizarre issues and learn something new by fixing it..

This time it was about how to use the object lock statement (C# lock / VB.NET SyncLoc, .NET Monitor.Enter) properly.

See I had this method that was acting as a callback handler form a Delegate.BeginInvoke call. I had a dataset that was to be updated by multiple threads, so I had written code like following

DataSet _sharedDataset = new DataSet();

 

private void PostProcessingCallback(IAsyncResult ar)

{

    object Mycallbacklockobject = new object();

 

    lock (_sharedDataset)

    {

        //add a few rows to the dataset

    }

 

    //do some more processing

 

    lock (_sharedDataset)

    {

         //add another few rows to the dataset

    }

 

    //do some more processing

 

    //finally accept changes           
    _sharedDataset.AcceptChanges()

 }

This code seemingly worked for most of the time, but every now and then the method world throw some strange exception about the dataset being inconsistent when AcceptChages() was called.

It was only after a while I realized that it was a big mistake to release the dataset from the lock in between while other processing went on.

When you release the dataset in the middle of processing as above, there is no guarantee that the same thread will again get the lock even if it the method has not yet stopped executing!

So to fix the problem I had to change the code to the following.

DataSet _sharedDataset = new DataSet();

 

private void PostProcessingCallback(IAsyncResult ar)

{

    object Mycallbacklockobject = new object();

 

    lock (_sharedDataset)

    {

        //add a few rows to the dataset

   

        //do some more processing

 

        //add another few rows to the dataset

   

        //do some more processing

 

        //finally accept changes

        _sharedDataset.AcceptChanges();

    }
}


The idea is to keep the shared object under lock till all processing is over.I never go any exception again about the dataset being inconsistent.

 

 

 

Friday, March 30, 2007

I've been programming in ASP (including ASP.NET) for almost seven years from now, and one of the most common problems was to ensure that certain pages should not be accessed without a session being active.

Usually this page depended the user being logged in, where we stored the fact (that the user is logged in) by keeping a flag in a session variable. Other common scenarios were when a page depended on some value that was derived or set on a different page and transported to the current page through a session variable.

The code to accomplish this was almost always clumsy. In its simplicity, you'd always check for existence of a session variable like following..

if (Session["MY_VAR_A"] == null)
    Response.Redirect("ExpiredPage.aspx");

If under certain conditions, your page would work if one of the various session variables was provided for, things would get clumsier.

For example, if in one condition, Session["MY_VAR_A"] was initialized somewhere and the flow came to this page and it was okay to continue processing, while in another condition it was okay to process if Session["MY_VAR_B"] had some value, you had to write code like following..

if (somecondition) 
   if (Session["MY_VAR_A"] == null)
      Response.Redirect("ExpiredPage.aspx");
else
      if (Session["MY_VAR_B"] == null)
         Response.Redirect("ExpiredPage.aspx");

The objective was the same, you wanted to ensure that if a new Session had to be created to process this page, then the user should be redirected to the Expired page or whatever...

ASP.NET Version 2.0 introduced a cool new property in HttpSession class  (Available through the Session property of the Page class), called IsNewSession, that can be used to check if a new session was created for the current request. This made things much simpler. All you have to do now is...

if (Session.IsNewSession) 
   Response.Redirect("ExpiredPage.aspx");

   
Now life is a little less complicated :)

 


 

   

 

 

 

Saturday, January 27, 2007

After reading a million blogs and procrastinating for some time, its time I start contributing back to the community which has helped me so much for all these years.

I plan to use this blog to share with the world...

1. My latest tech finds, articles, solutions, code anything.

2. Share some personal stuff.

3. Write a few articles explaining the approach I took to solve problems. Hope this goes a long way in helping fellow developers.

4. Anything new and wild and fascinating...