1. Extending the Threading Model
At
this point, we have created a multithreaded service by adding a worker
thread. However, we are currently limited to one worker thread. We’re
going to change that now.
We have
a few ways to resolve the fact that we’re using only one worker thread.
A couple of factors affect what we decide to do. First, we need to
consider the requirements of the problem we are trying to solve.
Currently we’re polling a file location to determine whether any .txt
files exist and, if they do, reacting to that information.
To determine the threading model we need, we have to ask several questions:
Can we monitor more than one file folder?
Can we monitor more than one file type per folder?
Can we move files of diffent types to different output folders?
Do we want to move more than one file at a time, per type, per folder?
Monitoring More Than One Folder
If
we were going to monitor the same type of files but have more than one
folder, monitoring them all by using one thread wouldn’t make much
sense. The framework that we have designed allows our service to have
multiple threads, all of which process one file type each.
Currently
we have one folder and one file type: .txt. We also have one worker
thread. However, we could easily create an array, a collection of
threads, all of which read from a separate folder simultaneously.
However, we need to fix a few things to make this work:
We need to add string resources for each folder being monitored.
We need to modify <ThreadFunc> to accept a parameter that would specify the folder location so that each thread monitors the proper folder.
We need to modify the class to create a collection of threads instead of a single worker thread.
<OnPause>, <OnContinue>, and <OnStop> stay the same because each thread already knows how to pause, continue, and stop based on the code already available.
Monitoring More Than One File Type Per Folder
You
may decide that not only do you have multiple file folders, but that
you also have multiple file types. Each file type can be processed
differently, even though they come frome the same initial incoming
folder.
For this scenario we need to make some more changes to the code:
We
need to update the data passed to the thread so that both the Incoming
folder and file type are monitored for, or a list of file types is
monitored for. This most likely means passing in a structure or class
with the information we need.
We need also need to update the <ThreadFunc> method to pass a new array of file type wild cards to the <GetFiles> call—which is only looking for .txt at this point.
Outputting to More Than One Folder
You
could decide that you want each file type to be processed or sent to a
different output folder. This has the following requirements:
Again we need to update the resource strings to have a list of possible output paths.
We need to update the <ThreadFunc>
method to take a parameter indicating where to move the file to instead
of the current parameter setting which is the OutgoingPath resource.
Processing More Than One File Type Per Folder
In
the final modification, we are going to create multiple threads per
incoming folder. This means that not only can we read from the same
folder with multiple threads, but we can output to multiple folders.
Each thread has to be configured with the incoming folder. Multiple
threads can share the same file type to monitor for. If the threads are
pointing to the same file type, we want to avoid having multiple
threads monitor the same folder, because of issues with
synchronization. However, when it comes to an output folder, we can
share a single output
folder among multiple threads, each thread writes differently named
files, with different file types, so there is no need for
synchronization.
When Complexity Steps In
One
scenario is complex: multiple threads per file type, per folder. This
means that more than one thread would be told to monitor a given
folder. The problem is that there is no way for one thread to know that
another thread is actively processing a given file. This might cause
the service to have a large number of errors, which would waste CPU
cycles—or you would spend more time with thread contention and failures
than you would processing anything useful.
How
do we get around this? In reality we don’t have many options. Any
option that allows more than one thread at a time to process (or in
this case, move) files will inadvertently end up fighting with another
thread. This means that we either accept having a single thread per
file type, per folder, or we create a master worker thread that looks
for all files on the disk and then places the files into a queue shared
between the producer thread and the consumer threads.
Although
this is definitely possible, you always want to ensure that you don’t
add too much complexity to your service just to be able to have more
than one thread. If you only have a very low volume of files, this
solution might not be worth the complexity or the processing overhead.
2. Adding a FileWorker Class
The
threads need access to the parameters and properties of file locations
and file types. To simplify this process, we’ll create a class that
we’ll use to create an instance of the worker thread. This class will
contain all the functionality and properties we need, and we won’t have
to add all the direct functionality to the service. You will also be
able to reuse this functionality in other applications that you design.
Designing a New Class File
Add a new class file to the Tutorials project and call it FileWorker. Then add the following imports to the top of the class definition, as shown in Listing 1.
Listing 1. Add the file require imports.
Imports System.Resources Imports System.IO Imports System.Text Imports System.Threading
|
Adding a Constructor
Next we want to add a constructor. The constructor can be blank for now, as shown in Listing 2.
Listing 2. Add a blank constructor to the new class.
Adding a Worker Thread
Now we want to add a single worker thread to the class, as shown in Listing 3. Later on we will have more than one worker thread.
Listing 3. Add the following thread to the new class.
Private m_WorkerThread As Thread = Nothing
|
Now
we need to add a property that grants access to the worker thread so
that we can join the thread from outside the class, as shown in Listing 4.
Listing 4. Expose the thread by adding a property.
Public ReadOnly Property WorkerThread() as Thread Get Return m_WorkerThread End Get End Property
|
We use the ReadOnly accessor because we don’t want users to be able to set this value; we only want them to be able read it.
Adding the Worker Thread Function
Because we will be replicating most of the functionality of the current service <ThreadFunc>, you can copy and paste it from the Tutorials.vb file into the FileWorker class file. We will not modify the function.
Adding the Thread Action State
Similar
to the service class, we need to be able to tell the worker class when
to pause, continue, or stop. Add a class file and call it threadactionstate.vb. Copy the ThreadActionState structure from the tutorials.vb and paste it into the new threadactionstate.vb class file.
We
are going to change the Thread Action State from a structure to a
class. We need to do this because we will be passing a reference to our
Thread Action State shared variable to the FileWorker
class instances. Remember to comment out the version that currently
exists in the Tutorials service class, unless you have already removed
it.
As shown in Listing 5, create a local reference in the FileWorker class.
Listing 5. Define the following thread state variable.
Private m_ThreadAction As ThreadActionState
|
Adding a WriteLogEvent
Copy the WriteLogEvent method from the Service class to the FileWorker
class. Although we could copy the event to our modService file and make
it publicly accessible, we are going to create a self-contained
reusable class.
Adding Our <Start> Method
The last thing we need to do now is to add our <Start> method. This method will be used to start the worker process <ThreadFunc> in each instance of our FileWorker class.
Listing 6. Create the following Start method.
Public Sub Start()
m_WorkerThread = New Thread(AddressOf ThreadFunc)
m_WorkerThread.Priority = ThreadPriority.Normal
m_WorkerThread.IsBackground = True
m_WorkerThread.Start()
End Sub