Creating Multi-Threaded Applications with the .NET 2.0 Framework

                   Sub Topics: Safe Synchronization for Multi-threaded Data Access and 
                                          Thread Safe Callbacks via Delegates

Threads with .NET are fairly easy to create and can be used for all sorts of tasks. A good example are background threads which send
and receive data to other machines on a network. Server programs utilizing TCP/IP typically spawn a dedicated listener thread to sit and
wait for incoming connections. Once the connection is completed the server thread will either pass the connection off to an existing worker
thread or it may spawn a new thread dedicated to service the incoming client. The following code snippets are from a compact framework
to full framework application I wrote to collect data on Mobile devices and push it up to a windows server application. All of the network
IO occurs in background threads. This keeps the GUI free and open on both the client and server ends to handle user interaction. The
first snippet is an example of creating TCP server and worker threads using the Thread class and the ThreadStart delegate.

class GenericTcpServer {
     private GenericServerTCPThreadObject GSTObject;
     private Thread GSTThread;
     public void GenericServerTCPStart() {
          GSTObject = new GenericServerTCPThreadObject();
          GSTThread = new Thread(new ThreadStart(GSTObject.GenericServerTCPWorkerThread));
          GSTThread.Start(); }
     public void GenericServerTCPStop() {
          GSTObject.GenericServerTCPWorkerThreadEnd();
          Thread.Sleep(2 * MiscConstants.ResetTimeout); } }

     class GenericServerTCPThreadObject {
          private TcpListener Listener;
          private bool ShutDown = false;
          public void GenericServerTCPWorkerThrefor Dataad() {
               Listener = new TcpListener(PortProp.Port);
               Listener.Start();
               while (true) {
                    while (!Listener.Pending()) {
                         Thread.Sleep(MiscConstants.ResetTimeout);
                          if (this.ShutDown) { 
                               Listener.Stop();
                               return; } }
                         GenericTcpConnectionThread nc = new GenericTcpConnectionThread(); 
                         nc.Listener = this.Listener;
                         Thread nt = new Thread(new ThreadStart(nc.ServerReceiveData));
                         nt.Start(); } }

               public void GenericServerTCPWorkerThreadEnd() { 
                    this.ShutDown = true; } }

               class GenericTcpConnectionThread {
                    public TcpListener Listener;
                    private static int NumberConnections;
                    public void ServerReceiveData() {
                         int Recv;
                         byte[] Data;
                         byte[] ResizeData;
                         int i = 0;
                         ArrayList DataList;
                         TcpClient tcpClient; 
                         try { tcpClient = Listener.AcceptTcpClient(); // this blocks! } 
                         catch (SocketException) {
                               Console.WriteLine("Accept Exception");
                               return; }
                         DataList = new ArrayList(); 
                         NumberConnections++;
                         while (true) {
                         Data = new byte[1024];
                         Recv = tcpClient.Client.Receive(Data, Data.Length, SocketFlags.None);
                         if (Recv <= 0) break;
                         // temp hold the data in an arraylist
                         if (Recv < 1024) {
                              ResizeData = new byte[Recv];
                              Buffer.BlockCopy(Data, 0, ResizeData, 0, Recv);
                              DataList.Add(ResizeData); } 
                         else DataList.Add(Data); i++; } 
                         tcpClient.Close(); // 
                         NumberConnections--; 
                         TransactionAccessC ta = new TransactionAccessC(); ta.
                         TransactionAccess(TransactionAccessC.WRITETRANSACTION, ref DataList, "N"); 
                         // we go away now } }

// The above is instantiated by a call from the Forms constructor

public partial class Form1 : Form { 
     private GenericTcpServer gts;
     public Form1() { 
     InitializeComponent(); 
     gts = new GenericTcpServer(); 
     gts.GenericServerTCPStart();
     // do work
     // ...
     // shut down
     gts.GenericServerTCPStop();
}

To quote from the class library reference "When a thread is created, the new instance of the Thread class is created using a constructor
that takes the ThreadStart delegate as its only parameter. However, the thread does not begin executing until the Start method is
invoked. When Start is called, execution begins at the first line of the method referenced by the ThreadStart delegate." I never realized
that ThreadStart was a  delegate. Special note for the VB folks again from the library reference, "Visual Basic users can omit the
ThreadStart constructor when creating a thread. Use the AddressOf operator when passing your method to the Thread constructor".

The above code simply waits on a configured port number for an incoming connection. Once connected a worker thread is spawned which
consumes the data and places it into an ArrayList. This is then passed onto a transaction queue (another piece of code not shown here)
Note: there is also a Thread.Sleep() method as well which will place the thread in an idle state for the specified number of milliseconds.

Along with threading go other issues such as synchronization and data access safety. In creating a windows server application I was
shocked  to see that the synchronization attribute did not work all of the time. After talking with a few people this was confirmed. Their
suggestions were to either  use the SyncRoot method along with the lock keyword or utilize a try catch model to handle the case where
the exception occurs. Actually I had done the latter in order to handle the error but even with that I had a creepy feeling about using the
synchronization attribute. Here is an example of a classic consumer-producer process which is reading and writing transactions. Different
threads call into this function. Note: the first example will fail occasionally, the second will not!

// * bad, bad, bad this will fail every once in a while
[MethodImpl(MethodImplOptions.Synchronized)]
public string TransactionAccess(int Type, ref ArrayList al, string cmd) {
     string str = "";
     switch (Type) {
          // from network
          case WRITETRANSACTION:
               TransactionQueue.PushItem(al);
          break;
          // from browser or other app
          case READTRANSACTION:
               str = ReadTransactionXML(ref al, cmd);
          break; }
     return str; }

// *** better practice
public string TransactionAccess(int Type, ref ArrayList al, string cmd) {
     string str = ""; 
     lock (al.SyncRoot) {
          switch (Type) {
          // from network
          case WRITETRANSACTION:
               TransactionQueue.PushItem(al);
          break;
          // from browser or other app
          case READTRANSACTION:
               str = ReadTransactionXML(ref al, cmd);
          break; } }
     return str; }

Note: In the above example SyncRoot comes with the ArrayList, but you can allocate and use a SyncRoot as a  System.Object
class. The lock keyword keeps the function locked between the code block enclosed by the braces.

Additionally you may want to have a thread call back into another thread to update data. For example a complex calculation has been
completed by a background thread and you want to display the result on the
Form. You can use delegates to do this but you will want to
marshal the data you are passing as well as ensure thread safety by using the Invoke method. Otherwise you will get an exception! 
Here is an example of a main thread creating a delegate and passing this as an instance member to a thread which it spawns. The thread
calls back the main thread via a delegate with the Invoke method in order to update its GUI with data.

namespace CollectorServerMobile {
     public delegate void TriggerFunction();
     public partial class Form1 : Form {
     private int item;
     private GenericTcpServer gts;
     private TransactionAccessC ta;
     private ArrayList al;
     private TransactionQueue.GenericXMLEmitter gxe;
     public TriggerFunction cb;

     public Form1() {
          InitializeComponent();
          cb = new TriggerFunction(theTriggerFunction);
          gts = new GenericTcpServer();
          gts.GenericServerTCPStart(this);
          gxe = new TransactionQueue.GenericXMLEmitter();
          gxe.GenericXMLEmitterStart();
          // do work
          // ...
          // shut down
          gts.GenericServerTCPStop(); 
           }
}

// the call back function - called by the thread
void theTriggerFunction() {
     string s;
     s = ta.TransactionAccess(TransactionAccessC.READTRANSACTION, ref al, "L");
     if (s == null) 
          return;
      item = int.Parse(s);
      if (al.Count > 0)
          DisplayFields(al);
}


// the callers constructor saves "this" as a generic object when instantiated

class GenericServerTCPThreadObject {
     private TcpListener Listener; 
     private object Caller;
     public GenericServerTCPThreadObject(object theCaller) {
          Caller = theCaller; }

// the caller thread, when ready to notify the main thread
// makes the call to the main form

//  do forever
//  read data from network
//   ... 
     Form1 someform = (Form1)Caller;
     someform.Invoke(someform.cb); 
//   ...
//   sleep



 

Note: this is similar to the code in the first example, although this time we are creating a delegate as a member field in the class. When we
spin the thread we pass "this" to it which then gives the thread the ability to make a callback to the delegate function. Remember that
delegates are just type safe function pointers. Unfortunately just calling the delegate from the thread creates chaos and if you do it your
program will perform the callback but will create an unhandled exception when it attempts to access any data in the called function. To
keep it clean use the Invoke function.

You can take this example further by creating multiple delegate functions to perform different operations as required by your
application.


                        Creating Multi-Threaded Applications with the 2,0 Framework                                           Device Programming                                          

                               

Feedback/Contact  paulzazzarino@3zwireless.com .

Copyright 2006 3zwireless Ltd, This page last updated on 03/2006