Timers

The Monitor sample that I've just demonstrated has one unfortunate problem that you might have noticed: I had to pass in a guessed value of 2.5 seconds as the time that the main thread needed to wait for the results. For the samples, that was a fairly safe bet because I knew exactly how long the results were going to take to come through. However, as a general solution that is not really satisfactory. If getting the address was really being done by asking a remote database for the value instead of - as in the sample - sleeping for one second then returning a hard-coded value, it would be very hard to know how long to wait.

There are two solutions to this problem:

  • We could set up a timer which polls the DataRetriever objects every so often to see if the results have arrived yet.

  • We could have the callback method in the DataRetriever objects somehow notify the main thread when the results are in.

In both cases, the easiest way to inform the main thread of the situation is via a ManualResetEvent.

In the particular situation of our samples, the second solution is more efficient. However, I'm going to go for the first solution because it lets me demonstrate the use of a timer as well as a ManualResetEvent. The first solution also has the advantage that the timer can display regular progress reports to the user.

Before I present the sample, a quick word about timers in general. A timer is an object that does something at a specified interval - for example, every half-second or every two seconds - and is invaluable for regularly polling to find out if something has happened. The Windows operating system implements a number of timers that are available to use via Windows API calls, and as you'd expect, the .NET Framework also defines some classes that implement timers, almost certainly by wrapping the Windows API timers. The .NET Framework actually offers three different timer classes, and just to confuse you they are all called Timer (although they are in different namespaces).

Timers represent one of the few cases in .NET where you may need to specify the fully qualified name of a class instead of just the class name.

  • System.Threading.Timer. This is the timer that we will use in the next sample. It works using a callback technique. At the specified interval, it invokes a delegate to a callback method (which will have been supplied to the Timer constructor). The callback method will be invoked on one of the thread pool threads - and there is no guarantee that successive callbacks will be executed on the same thread; the chances are they won't be. This timer has the advantage of accuracy, and is the one you should normally use if you are prepared to cope with multiple threads and need an accurate timer.

  • System.Windows.Forms.Timer. This timer relies on the Windows message loop for its functionality, and it does not use multiple threads. It simply raises a Control.Timer Windows Forms-style event at the specified intervals, and it's up to you to supply an event handler for this event. Although this timer is easy to use in the Windows Forms environment, it's not very accurate because the timer event handler can only be processed when the user-interface thread isn't doing anything else - this means that although the timer events are raised at accurate intervals, there may be a delay before each event is handed. Also, the timer will only work if the thread on which it is instantiated is a user-interface thread - which in most cases means a Windows Forms application. You should use this timer if accuracy isn't important, you are running a Windows Forms application (or some other application with a message loop), and you don't want to worry about multi-threading.

  • System.Timers.Timer. We won't say too much about this timer. It is intended for use in a server environment. It is multi-threaded and therefore accurate, but it also works by raising events (of the delegate type). This Timer is basically the ASP.NET equivalent of the Windows Forms timer (it isn't restricted to ASP.NET, but it's most useful in a designer environment).

The TimerDemo Sample

This sample is a development of the previous Monitor sample, in which we use a timer to poll the array of DataRetrievers every half-second to see if all results are present. If any results have not arrived yet, it displays a message indicating how many results we are still waiting for. If all results have arrived, it displays the results and the program terminates.

This sounds reasonably simple, but there is a complication: System.Threading.Timer callbacks execute on a background thread-pool thread. And background threads do not have any direct influence over the termination of the process. OK, we could do something like call System.Diagnostics.Process.Kill(), but that is a bit of a drastic solution, and in general not to be recommended because it doesn't allow other threads to do any cleanup. In the normal course of execution, the process will end when the main thread exits the Main() method (provided there are no other foreground threads). If the timer callback were being executed on that thread, this would be trivial to achieve. But it isn't - and that means that for proper program termination, the callback thread will somehow have to communicate to the main, foreground, thread to tell it when it's OK to exit the process. The way to do this is using a ManualResetEvent (actually, for this particular sample it would make no difference if we used an AutoResetEvent instead, since we don't care what happens to the event once it's signaled, but we'll use the ManualResetEvent anyway). The code will work by instantiating the ManualResetEvent and setting it to the non-signaled state. The main thread, having set up the timer loop, uses the ManualResetEvent.WaitOne() method to block its own execution until the event is signaled. Meanwhile, when the callback function running on the background thread has detected that the results are ready, it calls the ManualResetEvent.Set() method to signal the event. This of course immediately unblocks the main thread, which can proceed to display the results and exit the Main() method, causing the process to terminate.

That's the theory; let's see the practice. First, here is the new-look Main() method, with the changes highlighted:

 public static void Main() {    Thread.CurrentThread.Name = "Main Thread";    DataRetriever[] drs = new DataRetriever[3];    string[] names = { "Simon", "Julian", "Wrox Press" };    for (int i=0; i<3; i++)    {       drs[i] = new DataRetriever(names[i]);       drs[i].GetAddressAsync();    }    ManualResetEvent endProcessEvent = new ManualResetEvent(false);    CheckResults resultsChecker = new CheckResults(endProcessEvent);    TimerCallback timerCallback = new TimerCallback(                                         resultsChecker.CheckResultStatus);    Timer timer = new Timer(timerCallback, drs, 0, 500);    endProcessEvent.WaitOne();    EntryPoint.OutputResults(drs); } 

Once the main thread has sent off all the queries for addresses, it instantiates the ManualResetEvent. The Boolean parameter passed to the ManualResetEvent constructor indicates whether the event should start off in the signaled state. We don't want that here, since the main thread will need to wait for the event to be signaled:

 ManualResetEvent endProcessEvent = new ManualResetEvent(false); 

Next we instantiate a CheckResults object, passing to its constructor the event we've just created. CheckResults is a class we are going to define to handle the timer callbacks. We instantiate a timer (a System.Threading.Timer object in this case - there's no name clash since this sample doesn't import either the System.Windows.Forms or the System.Timers namespace). We also define a delegate that will wrap the callback method (that will be a method in the CheckResults class called CheckResultStatus()), and we instantiate the timer:

 CheckResults resultsChecker = new CheckResults(endProcessEvent); TimerCallback timerCallback = new TimerCallback(                                      resultsChecker.CheckResultStatus); Timer timer = new Timer(timerCallback, drs, 0, 500); 

There are a couple of points to note about this code. The Timer class has a number of constructors, but the one we use here takes four parameters. The first is the delegate that indicates the callback method. This delegate must be of type TimerCallback.TimerCallback is defined in the System.Threading namespace as follows:

 public delegate void TimerCallback(object state); 

It returns void (as you'd expect - what would the timer do with any return value?), and takes an object as a parameter. This object is the same object that you pass in as the second parameter to the Timer constructor. It is there to contain any state information that you want to make available to the callback method. In this case, we're passing in the DataRetriever array - the callback method needs access to this array if it is to be able to check what results are in so far.

The third and fourth parameters to the Timer constructor are the delay time and interval time, both in milliseconds. In other words, these represent the number of milliseconds before the first time that the callback method is called (we've indicated zero, so it will be called straight away), and the frequency with which it will be called - here we've specified every 500 ms, or every half a second.

Next the main thread simply sits back and waits for the ManualResetEvent to be signaled, at which point it can display the results and exit the program:

 endProcessEvent.WaitOne(); EntryPoint.OutputResults(drs); 

Now for the CheckResults class. First we'll examine the constructor and the member field that is used to store the reference to the ManualResetEvent object created on the main thread:

 class CheckResults {    private ManualResetEvent endProcessEvent;    public CheckResults(ManualResetEvent endProcessEvent)    {       this.endProcessEvent = endProcessEvent;    } 

Now for that callback method:

 public void CheckResultStatus(object state) {    DataRetriever [] drs = (DataRetriever[])state;    int numResultsToGo = 0;    foreach(DataRetriever dr in drs)    {       string name;       string address;       ResultStatus status;       dr.GetResults(out name, out address, out status);       if (status == ResultStatus.Waiting)          ++numResultsToGo;    }    if (numResultsToGo == 0)       endProcessEvent.Set();    else       Console.WriteLine("{0} of {1} results returned",                         drs.Length - numResultsToGo, drs.Length);    } } 

The code in this method is relatively simple. It first loops through the DataRetriever objects in the array, counting how many of them have still got the status set to ResultStatus.Waiting. If this number is greater than zero, it displays a message telling the user how many results are in so far. If the number is zero, then we know we've finished, so we signal the ManualResetEvent. This last action is the crucial new piece of code:

 if (numResultsToGo == 0)    endProcessEvent.Set(); 

Finally, I'm going to sneak in one change in the DataRetriever class. Just to make the output from the timer sample more interesting, I'm going to throw in an extra delay for the case where the name isn't found in the database (a realistic scenario):

 // In DataRetriever.GetAddress ThreadUtils.DisplayThreadInfo("In GetAddress..."); // Simulate waiting to get results off database servers Thread.Sleep(1000); if (name == "Simon")    return "Simon lives in Lancaster"; else if (name == "Wrox Press")    return "Wrox Press lives in Acocks Green"; else {    Thread.Sleep(1500);    throw new ArgumentException("The name " + name +                                " is not in the database"); } 

Running the TimerDemo sample gives these results:

 In GetAddress...  hash: 15, pool: True, backgrnd: True, state: Background In GetAddress...  hash: 20, pool: True, backgrnd: True, state: Background In GetAddress...  hash: 23, pool: True, backgrnd: True, state: Background 1 of 3 results returned 1 of 3 results returned 1 of 3 results returned 1 of 3 results returned 2 of 3 results returned 2 of 3 results returned 2 of 3 results returned Name: Simon, Status: Done, Result: Simon lives in Lancaster Name: Julian, Status: Failed, Result: The name Julian is not in the database Name: Wrox Press, Status: Done, Result: Wrox Press lives in Acocks Green 



Advanced  .NET Programming
Advanced .NET Programming
ISBN: 1861006292
EAN: 2147483647
Year: 2002
Pages: 124

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net