Friday, July 11, 2008

Visual Studio Threading – Part I

This is a part of a series of articles on my experiences with Visual Studio Threading and Advantage Events.

I want to make it clear that I am not an expert on threading and it is something I am just diving into. It is something that will be very useful when working with Advantage Events, which are the topic of this months Tech Tip. This the first posting on what I have learned about threading while developing some samples which demonstrate using Database Events.

I began my research on Google looking for some articles on threading with Visual Studio. There are quite a few good articles available but the most complete one I found is called “Threading in C#” by Joseph Albahari, which is an excerpt from the book C# 3.0 in a Nutshell. If you prefer using VB.NET you can find a good introduction on VB.NET Heaven and the articles available from MSDN provide both VB and C# samples.

Most of the background information I read on threading made it a point to state that you can make big mistakes when threading. I believe this advice is intended to mean implement your threading very carefully but often comes off as you probably shouldn’t be doing this. Mr. Albahari had a more positive message, he states:

Having multiple threads does not in itself create complexity; it is the interaction between the threads that creates complexity

For a more biased opinion I went straight to Microsoft to see what they had to say about threading within Visual Studio. The article discusses thread design and still manages to discourage it at the same time. As I expected threading is a complex subject but the.NET framework would have a good solution. As it turns out there are many ways to do threading with Visual Studio most of which are encapsulated in the System.Threading namespace. Once I started digging into the Thread, ThreadPool and BackgroundWorker classes I had to run out and get some duct tape to prevent my head from exploding.

With some duct tape on hand I was ready to do some testing to see which approach would be best. In this post I will discuss using the Thread class. As it turns out it is pretty easy to implement and it runs pretty seamlessly at least until you need the thread to interact with the User Interface. Controls can only be updated by the thread they are created on, in this case the main application thread. However, controls have an Instance() method which can be used across threads, more on this later.

Multiple foreground threads can be created but each of them must be stopped before the main thread otherwise the UI will stop responding. Threads can also be run in the background independent of the main program thread. buy specifying the IsBackground property. This is very handy since I wanted to use a background thread to run the sp_WaitForEvent system procedure. However, I still had to find a way to signal the main thread that I had received an event and that some sort of action needed to be taken.

One of the mechanisms is to use the Lock statement, which creates a critical section, to allow objects to be accessed by multiple threads. The lock ensures that only one thread at a time can manipulate the object. You can also use a technique called Wait and Pulse to signal between threads. I had to get out the duct tape when I read about it so I started looking for an alternative.

Which brings me back to the Invoke() method. This method is available on most controls and causes a Delegate to be run on the thread which created the control. This proved to be very useful since I can now tell the main application thread to run some code which updates a control based on the event that my background thread received.

So putting it all together my experiment with the Thread class looked like this:

   1: // Delegate for thread interaction
   2: private delegate void UpdateDelegate();
   3: private UpdateDelegate myUpdate;
   4:  
   5: private void UpdateRooms()
   6: {
   7:     // Update the rooms dataset
   8:     dsRooms.Tables[0].Clear();
   9:     daStatus.Fill(dsRooms);
  10:     dgvStatus.DataSource = dsRooms.Tables[0];
  11:     dgvStatus.Refresh();
  12:     lblStatus.Text = "Updated: " + DateTime.Now.ToString("HH:MM:SS");
  13: }
  14:  
  15: // Function which indefinately for a specific event
  16: private void WaitForEvent()
  17: {
  18:     AdsConnection cn;
  19:     AdsCommand cmd;
  20:     AdsDataReader dr;
  21:     
  22:     // We need a new connection to wait for events 
  23:     // sConn is the connection string used by the application
  24:     cn = new AdsConnection(sConn);
  25:     cn.Open();
  26:     cmd = cn.CreateCommand();
  27:  
  28:     try
  29:     {
  30:         cmd.CommandText = "EXECUTE PROCEDURE sp_CreateEvent('RoomStatus', 0)";
  31:         cmd.ExecuteNonQuery();
  32:     }
  33:     catch (AdsException aex)
  34:     { 
  35:         Console.WriteLine("Error: " + aex.Message);
  36:         return;
  37:     }
  38:  
  39:     // Make sure the command doesn't time out
  40:     cmd.CommandTimeout = 0;
  41:  
  42:     // Wait for RoomStatus event
  43:     cmd.CommandText = "EXECUTE PROCEDURE sp_WaitForEvent('RoomStatus', -1, 0, 0)";
  44:     
  45:     try
  46:     {
  47:         dr = cmd.ExecuteReader();
  48:  
  49:         // check to see if the room status was updated
  50:         dr.Read();
  51:         if (dr.GetInt32(1) > 0)
  52:         {
  53:             // Let the main thread know we have an event
  54:             dgvStatus.Invoke(myUpdate);
  55:         }
  56:  
  57:         dr.Close();
  58:     }
  59:     catch (Exception)
  60:     {
  61:         cmd.Cancel();
  62:     }
  63:     finally
  64:     {
  65:         // Clean up the event
  66:         cmd.CommandText = "EXECUTE PROCEDURE sp_DropEvent('RoomStatus', 0)";
  67:         cmd.ExecuteNonQuery();
  68:  
  69:         // Close the connection
  70:         cn.Close();
  71:     }
  72: }

Now the application can create a new thread and run the WaitForEvent function on that thread. When an event is received by the thread it calls the Invoke() method for a DataViewControl on the main thread which executes the UpdateRoom function. At this point I would need to create a new thread to run WaitForEvent again since threads cannot be restarted after they finish. This is a bit inefficient so creating a loop within the thread to run the sp_WaitForEvent after an event was received would reduce the overhead.

One of the issues with this implementation is cleaning up the background thread when the application exits. This can be done with the Abort() method but this can cause problems. Since the thread is waiting for a response from the server, i.e. the command object is still active, the Abort() method stops the command object raising an error. For some reason this error is not easy to capture so I am still looking for a more graceful way to shutdown the thread. Thoughts on this matter would be appreciated.

I also experimented with using a BackgroundWorker class which wraps a lot of this functionality and has support for events which are a bit nicer to use than the Invoke() method. I’ll discuss my experience with the BackgroundWorker in another post.

No comments: