Scheduling
After installation, perhaps the most important aspect of the service is determining how and when the service will perform its work. To solve this problem, there are two basic architectures that are typically employed: using a timer and using a callback.
Using a timer, perhaps the most common technique, involves creating an instance of the System.Timers.Timer class, setting up an event handler to catch the Elapsed event, configuring the interval, and starting the timer. This is most typically done in the OnStart method of the service class that is called by the SCM each time the service is started from the Services MMC (found in the Administrative Tools group and the Control Panel). As shown in Listing 1, when the timer elapses, its event handler is called. When this occurs, the event handler should stop the timer, call a private method that performs the work (for example, looking for new files to process), and then restart the timer. In this way, the timer won't keep elapsing while work is being performed. A variation on this technique involves delegating the work (the processWork method in Listing 1) to a background thread. This can be easily accomplished using an asynchronous delegate (see Chapter 4 of Building Distributed Application with Visual Basic .NET). However, when using the latter technique, you'll need to be careful that multiple threads in the service don't conflict with one another during processing.
Of course, as you might have guessed, the timer interval is a great candidate for a registry entry created by the installer class mentioned above. In this case, you'll need to create a bit of code in the service class to read the registry values before starting the timer.
The second architecture involves using a callback. Essentially, this means that rather than poling for work as with a timer, the service waits to be called when work is available. Using this architecture, there are three primary scenarios you'll likely encounter:
Waiting for an MSMQ Message. The MessageQueue class found in the System.Messaging namespace can be used to monitor an MSMQ queue, either synchronously and asynchronously (see Chapter 14 of Building Distributed Application with Visual Basic .NET). Using these methods (Receive and BeginReceive) from the OnStart event, the service class can simply wait until new messages arrive in a particular queue and then pull them off for processing.
Waiting for File System Notification. Many services need to process files as they appear on the file system. Although poling using a timer is an effective approach, the service class can alternatively use the FileSystemWatcher class of the System.IO namespace (see Chapter 12 of Building Distributed Application with Visual Basic .NET). Using an instance of FileSystemWatcher, the service class can be notified of file system events such as created, deleted, renamed, and changedbased on criteria that include attributes, creation time, directory name, file name, last access, last write, security, and size. Once again, the service class can simply sit back in its OnStart event and wait to be notified of events by the file system.
Waiting for Remote Invocation. It turns out that Windows services make great hosts for .NET objects that can be activated from other processes using .NET Remoting (see Chapter 8 of Building Distributed Application with Visual Basic .NET). In this scenario, a Windows service would wait for a call from a remote process using either a TCP or HTTP channel. When the call arrives, the .NET Remoting infrastructure will instantiate the object in the Windows service process, allowing the remote process to call its methods and properties. When using Windows services in this way, you need to be well aware of the various ways in which .NET objects can be remoted and activated.