16.3. Stopping a Thread Safely in C#

Java's stop method was deprecated because upon its call, an unchecked java.lang.ThreadDeath error will propagate up the running thread's stack, unlocking any locked monitors as it goes along. As these locks are being indiscriminately released, the data they are protecting may be left in an inconsistent state. The same problem applies to C#'s Abort method. Thus, the recommended way of stopping threads in C# is not through the Abort method. Instead, the recommended way is to use a state variable that another thread can use to signal the current thread that it's time to die. Another way is to let a thread run its course.

In the earlier examples, to illustrate the various C# methods we use the Abort method to signal the killing of the thread. But we recommend that you do not use the Abort method to kill manually created threads. Better yet, as we will explain, you should avoid creating threads manually, thereby avoiding this problem.

Creating threads manually using the System.Threading.Thread class gives maximum flexibility because the thread creator can set various properties of the thread. However, the creator must make sure that the created thread does not cannibalize system resources and therefore must provide for a way to monitor and manage the thread. Also, as discussed, many applications create threads that spend a great deal of time in the sleeping state, waiting for an event to occur. Other threads may enter a sleeping state only to be awakened periodically to poll for a change or update status information.

Following are rough guidelines for when to create and not to create threads using the System.Threading.Thread class.

You should use manually created threads under these circumstances:

  • When you need finer control over the thread to accomplish a task

  • For coarse-grained tasks that in turn monitor other asynchronous, “bulk,” fine-grained tasks

  • For long-running, asynchronous tasks

You should not use manually created threads under these circumstances:

  • To handle bulk, fine-grained, short-running tasks (for example, servicing 10,000 requests)

  • To handle repetitive, time-controlled tasks for which the System.Threading.Timer class is more suitable

  • For fine-grained tasks that can be encapsulated as a delegate begininvoke or a threadpool waitcallback

16.3.1. Creating Threads Using the System.Threading.ThreadPool Class

Manually creating threads can hog system resources if the created thread is not managed properly. In Java, the onus of maintaining a thread's life cycle is on the programmer. C# gets rid of this error-prone approach by providing a first-class ThreadPool class. Thread pooling enables a more efficient use of threads and does not require the programmer to worry about the thread's life cycle because it is maintained by the thread pool.

A C# ThreadPool provides a pool of threads that can be used to post work items, process asynchronous I/O, wait on behalf of other threads, and process timers. One thread monitors the status of several wait operations queued to the thread pool. When a wait operation completes, a worker thread from the thread pool executes the corresponding callback function.

You can also queue work items that are not related to a wait operation. To request that a work item be handled by a thread in the thread pool, you call the QueueUserWorkItem method. This method takes as a parameter a reference to the method or delegate that will be called by the thread selected from the thread pool. There is no way to cancel a work item after it has been queued.

Timer-queue timers and registered wait operations also use the thread pool. Their callback functions are queued to the thread pool.

The thread pool is created the first time you create an instance of the ThreadPool class. The thread pool has a default limit of 25 threads per available processor, a limit you can change using CorSetMaxThreads, as defined in the mscoree.h file. Each thread uses the default stack size and runs at the default priority. Each process can have only one operating system thread pool.

Listing 16.8 shows how to use the QueueUserWorkItem method of the ThreadPool class. In a scenario where a thread A is spawned and does some activity, it is often required that thread B (which spawned thread A) needs to know when thread A is finished with the activity. To achieve this communication in Java, you would use the wait/notify mechanism or, in some cases, use Boolean flags to signal the completion. C# provides the concept of an AutoResetEvent class. AutoResetEvent notifies one or more waiting threads that an event has occurred. AutoResetEvent remains signaled until a single waiting thread is released, at which time the system automatically sets the state to nonsignaled. If no threads are waiting, the state remains signaled.

Listing 16.8. Using ThreadPool to Queue Callbacks (C#)
using System;
using System.Threading;

public class Test {

  public static void Main(string[] args) {
   for (int i =0; i < 50; ++i) {
     AutoResetEvent notify = new AutoResetEvent(false);
     ThreadPool.QueueUserWorkItem(
     new WaitCallback(AsyncOper),false);
     notify.WaitOne();
   }
  }

  // The callback method's signature MUST match that of a
  //System.Threading.WaitCallback
  // delegate (it takes an Object parameter and returns void)

  static void AsyncOper(Object state) {
    Console.WriteLine("WorkItem thread:
      Performing asynchronous operation.");
    // Sleep for 1000 milliseconds to simulate doing work
    Thread.Sleep(1000);
    // Signal that the async operation is now complete.
    ((AutoResetEvent)state).Set();
    Console.WriteLine("Finished the async operation");
  }

}

In Listing 16.8 the signature of the delegate called asynchronously should match the signature of the WaitCallBack delegate. The notify.WaitOne blocks the Main thread until all the async operations are completed.

Note that we dispatched 50 operations to the ThreadPool, whose capacity is 25 threads. The ThreadPool class takes care of managing the worker threads, and all we have to do is provide the callback method that ThreadPool needs to run. Had we not used the ThreadPool class, we would have had to create one Thread for each dispatched operation. This would be wasteful if this example were used to handle, say, 1,000 concurrent operations.

Listing 16.9 shows a combination of the Thread class and the ThreadPool class to depict a somewhat realistic class in which there is a Server class executing queued jobs. The Server itself runs a thread that allocates queued jobs to a ThreadPool. A main Thread enqueues the job for the Server.

Listing 16.9. The Server Class Using a ThreadPool
using System;
using System.Collections;
using System.Threading;

public class Server {

  public static int MAX = 1000;
  Thread mainThread;
  Queue jobs;
  int dequeued =0;


  public Server() {
    mainThread = new Thread(new ThreadStart(JobAllocate));
    mainThread.Name = "MyServer";
    jobs = new Queue();
    mainThread.Start();
  }

  public void AddJobs(object job) {
    lock (jobs) {
      jobs.Enqueue(job);
    }
  }

  public void JobAllocate() {
    while (true) {
      lock (jobs) {
          while (jobs.Count > 0) {
            ThreadPool.QueueUserWorkItem(
                new WaitCallback(DoJob), jobs.Dequeue());
          }
      }
      if (dequeued >= MAX) break;
    }
  }

  public void DoJob(Object state) {
    Console.WriteLine("Finished job "+state);
    ++dequeued;
  }

  public static void Main(string[] args) {
    Server server = new Server();
      for (int i = 0; i < Server.MAX; ++i) {
        server.AddJobs("Job "+i);
      }
  }
}

Note that we must put an exit condition in the JobAllocate() method for the main Thread to exit after processing the MAX number of jobs.

Following are rough guidelines of when to use and not to use thread pools.

You should use a thread pool under the following circumstance:

  • For handling bulk, fine-grained tasks of short duration.

You should not use a thread pool under the following circumstance:

  • For handling long-running, coarse-grained tasks. You don't want any thread pool threads to be dedicated for any one task.

In the example we used the lock keyword. This is a synchronization primitive of C# similar to the synchronize keyword in Java. We discuss thread synchronization in more detail in Section 16.4.

16.3.2. Creating Threads Using the System.Threading.Timer Class

There are times when an activity must be run in the background periodically. An example is that of a script running on your machine that trims the size of the log files of your application every day at midnight. You can use System.Threading.Timer to create threads that run and execute a particular action periodically. This is similar to the java.util.Timer class in its functionality.

Listing 16.10 is a slightly different version of our Server class using the Timer utility. Here, instead of the server handing off the jobs to a thread pool, a timer thread executes the queued jobs at regular intervals.

Listing 16.10. The Server Class Using the Timer Utility (C#)
using System;
using System.Threading;
using System.Collections;

class Server {
  Timer timer = null;
  Queue jobs;

  public Server() {
    jobs = new Queue();
    TimerCallback timerDelegate = new TimerCallback(DoJob);
    timer = new Timer(timerDelegate, null, 1000, 1000);
  }

  public int Count() {
    lock(jobs) {
      return jobs.Count;
    }
  }
  public void Dispose() {
      timer.Dispose();
  }
  public void DoJob(object state) {
    lock (jobs) {
      if (jobs.Count > 0) {
        Console.WriteLine("Finished job "+jobs.Dequeue());
      }
    }
  }
  public void AddJob(object job) {
    lock (jobs) {
      jobs.Enqueue(job);
    }
  }
  public static void Main(string[] args) {
    Server server = new Server();
    for (int i =0; i < 60; ++i) server.AddJob("Job "+i);
    while (server.Count() > 0) {
      Thread.Sleep(0);
    }
    server.Dispose();
  }
}

Note that the timer executes the task on a system-allocated thread much as the Java timer class does. The Server disposes of the timer thread after it has finished executing the tasks. The timer delegate indicates the method that needs to be periodically executed (in this case, every 1 second after an initial delay of 1 second). Note that the timer-initiated thread and the main thread must lock on the jobs queue to achieve predictable results. The timer class should be used for executing tasks that are periodic.

16.3.3. Creating Threads Using the Asynchronous Callback

Both ThreadPool and the Timer class use specific callback delegates as a mechanism to pass information between the caller thread and the execution thread. The same mechanism is provided in a generic manner for all delegates and can be used for attaining simple asynchronous processing (which is essentially the goal of thread programming).

Listing 16.11 shows how to create a simple asynchronous class with a simple callback delegate. The callback function can obtain a handle to the delegate by casting the IAsyncResult to the specific delegate.

Listing 16.11. Hello World Using C# Asynchronous Callback Delegates
using System;
using System.Threading;

public class MyAsyncCaller {

  public delegate string MyDelegate(string s);

  public void MyCallback(IAsyncResult iar) {
    MyDelegate del = (MyDelegate) iar.AsyncState;
    string result = del.EndInvoke(iar);
    Console.WriteLine(result);
  }

  public void Test(MyDelegate func, string param) {
    AsyncCallback cb = new AsyncCallback(MyCallback);
    func.BeginInvoke("World", cb, func);
  }

  public static string SayHello(string s) {
    return "Hello "+s;
  }

  static void Main(string[] args) {
    MyAsyncCaller caller = new MyAsyncCaller();
    caller.Test(new MyAsyncCaller.MyDelegate(SayHello),
    "World");
    Thread.Sleep(2000);
  }
}

The Main method creates an instance of MyAsyncCaller, which is a wrapper class for doing all our asynchronous calls. The Test method of that class takes the delegate and the parameter that will be passed to the delegate. The delegate passed to the Test method is a member of the MyAsyncCaller class and is called MyDelegate. The function passed is the SayHello function, which has the same signature as the MyDelegate delegate. It is this SayHello function that gets called asynchronously. AsyncCallback is a delegate provided by C#. To this delegate we pass our Callback method, which is the MyCallback method.

Inside the MyCallback method the result is cast to the delegate, and EndInvoke essentially invokes the SayHello method. The result of that method is then printed. Because there is no synchronization in this method, we must call Thread.Sleep to make sure the callbacks execute before Main finishes. To get rid of the seemingly hacky Thread.Sleep, we can use WaitHandles, as shown in Listing 16.12.

Listing 16.12. Hello World Using C# Asynchronous Callback Delegates and WaitHandles
using System;
using System.Threading;

public class MyAsyncCaller {
  public delegate string MyDelegate(string s);

  public void MyCallback(IAsyncResult iar) {
    MyDelegate del = (MyDelegate) iar.AsyncState;
    string result = del.EndInvoke(iar);
    Console.WriteLine(result);
  }

  public WaitHandle[] Test(MyDelegate func, string param) {
    AsyncCallback cb = new AsyncCallback(MyCallback);
    IAsyncResult res = func.BeginInvoke("World", cb, func);
    WaitHandle[] array = new WaitHandle[1];
    array[0] = res.AsyncWaitHandle;
    return array;
  }

  public static string SayHello(string s) {
    return "Hello "+s;
  }
  static void Main(string[] args) {
    MyAsyncCaller caller = new MyAsyncCaller();
    WaitHandle[] arr = caller.Test(new
      MyAsyncCaller.MyDelegate(SayHello), "World");
    WaitHandle.WaitAll(arr);
  }
}

The WaitHandle.WaitAll() makes the Main thread wait until all the asynchronous calls complete.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.67.26