A hardware monitor in C# 4.0 with the Task Parallel Library – Part 2 – Exception handling

Welcome back at part 2 in this series. In the last part I described the basic framework for the hardware monitor. In this part I will add some exception handling to the monitor and also a way to provide the monitor with extra configuration parameters.

The code can be found here, in the “Part 2” folder.

Exception handling

We would like to stop the main task when an exception is thrown and log the information.
Remember the continuation task we defined earlier to handle the main task cancellation. We can extend this that it also handles exceptions.

 this.readerTask.ContinueWith(t =>
 {
     if (t.IsFaulted)
     {
        t.Exception.Handle((x) =>
        {
           Console.WriteLine("Exception in task: {0}", x);
           return true;
        });

        try
        {
           CleanUp();
        }
        catch (Exception ex)
        {
           Console.WriteLine("Cleanup exception: {0}", ex.Message);
        }
    }
   //Notify everyone that we stopped
   OnStatusChanged(MonitorStatus.STOPPED);
   Console.WriteLine("Reader task stopped");
},TaskContinuationOptions.NotOnRanToCompletion);

When an exception in a Task is thrown, the status of the task is set to IsFaulted. So our continuation task should also be executed if the main task is in the faulted state. We accomplish this by changing the TaskContinuationOptions to TaskContinuationOptions.NotOnRanToCompletion, so that it executes when the main task is cancelled or is in the faulted state.

All exceptions thrown in a Task are collected by the TPL and put in one exception of type AggregateException (see here).
The Exception property of a Task contains this AggregateException. It exposes also a Handle method which has a delegate as parameter.
This delegate is executed for every exception in the AggregateException. In this delegate you can put your exception handling code, for example write it to log file or in the snippet above, write it to the console. (the X is the parameter to the delegate and is the current exception being processed) It should return True to indicate that this particular exception is handled, if it returns false the exception is further thrown down the stack.

After handling the exceptions, the CleanUp() method is called to ensure the monitor can be started again in a correct way.

Configuration parameters

It is not unthinkable that you want to pass some configuration parameters to your monitor, like baudrates, communication ports etc.
To add this functionality we have to change the interface a little bit:

public interface IDeviceMonitor<TConfigData, TDeviceData> : IDisposable
{
  void Start(TConfigData configData);
  ...
}

An extra type parameter (TConfigData) is added which represents a class (or even just an int or double or whatever)  that defines the configuration data you want to use.
When calling the Start method you pass an instance of this class.

Our base class also changes a bit, because, obviously, you want to work with the configuration data in your derived class:

public abstract class DeviceMonitorBase<TConfigData, TDeviceData> : IDeviceMonitor<TConfigData, TDeviceData>
{
  ..
  protected TConfigData configData;    
  public void Start(TConfigData configData)    
  {    
      this.configData = configData;
 ..

Example

I improved the example implementation a bit. I added a second timer monitor (TimerMonitorWithException) which throws an exception when the number of seconds is equal to an user specified number. You can pass this number as an argument to the Start() method. It therefore demonstrates the exception handling AND the use of configuration parameters.

I also extended the original timer monitor so that it uses a configuration parameter. Here you can adjust the interval of  printing the current time to the console.

This was part 2 of this series.  I hope you enjoyed reading this as much as you enjoyed the first part.

Happy coding and until next time!