Creating a Multithreaded Version
To execute code on a new thread, the code must be started using a subroutine that takes no parameters, which makes it a little problematic to start any background process that needs to be supplied information to run. The solution to this is to create a class that represents your background process that has at least one method, often called Start, which takes no parameters and can be used to run your process.
Now that your code is part of a class, you can add properties to that class to handle any information that you would have sent as a parameter, and then you can set those properties before starting a new thread with the Start method. For my file-scanning example, I will have just one property on my new class, holding the filter (*.wma, for example) that is to be used for my scan.
The other major change to my code will involve how the scan results are returned, a task currently being handled through the use of a global ArrayList instance. As part of the original goal, I wanted the filenames to load into the list as they were found, so the code needs to be modified so that it is returning the list of files progressively instead of all at once. With those two requirements, returning file names as they are found and removing the dependence on a global ArrayList, I am left with a couple of appropriate options. The simplest way to accomplish my goals is to raise an event from my new class and then handle each newly found file in the event-handler procedure back on the Form. Using these new design decisions and my existing code, I can create a new FileScan class, shown in Listing 2.
Listing 2: Creating a Class from Our Prototype Code
Imports System.IO Public Class FileScan Public Event FoundFile(ByVal fileName As String) Private fileFilter As String Public Property Filter() As String Get Return fileFilter End Get Set(ByVal Value As String) fileFilter = Value End Set End Property Public Sub StartScan() Dim directoriesToSearch As String() Dim currentDirectory As String directoriesToSearch = Directory.GetLogicalDrives() For Each currentDirectory In directoriesToSearch SearchForFiles(currentDirectory, True, fileFilter) Next End Sub Private Sub SearchForFiles(ByVal dir As String, _ ByVal subdirectories As Boolean, _ ByVal filter As String) Dim foundFiles As String() Dim foundFile As String Try foundFiles = Directory.GetFiles(dir, filter) For Each foundFile In foundFiles RaiseEvent FoundFile(foundFile) Next If subdirectories Then Dim foundDirs As String() Dim currentDir As String foundDirs = Directory.GetDirectories(dir) For Each currentDir In foundDirs SearchForFiles(currentDir, _ subdirectories, filter) Next End If Catch 'If I'm not allowed access 'to a file or folder due to 'security or a missing volume, 'then I want to just ignore it. End Try End Sub End Class
I use the Directory.GetLogicalDrives() method in the previous code to find all of the logical drives (such as A and C) for searching, but it would be more useful if you could specify a directory or set of directories to start searching.
Then, in my form, I can use this new class by creating an instance, setting the Filter property, and calling the StartScan() method. By declaring my new class at the Form level and including the WithEvents keyword, I can also handle the FoundFile event and use that event as my way to populate the ListBox:
Dim WithEvents myScanner As FileScan Private Sub frmWMAList_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles MyBase.Load lbFiles.Sorted = True myScanner = New FileScan() myScanner.Filter = "*.wma" myScanner.StartScan() End Sub Private Sub myScanner_FoundFile(ByVal fileName As String) _ Handles myScanner.FoundFile lbFiles.Items.Add(fileName) End Sub
This code is still working on a single thread, but it is now ready to be converted to run as a background process with very little work. The two steps needed to run this scan on a new thread are to create the thread, giving it a delegate to the StartScan method that is to be run, and to then start the new thread running:
Private Sub frmWMAList_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles MyBase.Load lbFiles.Sorted = True myScanner = New FileScan() myScanner.Filter = "*.wma" Dim myThread As New _ System.Threading.Thread(AddressOf myScanner.StartScan) myThread.Start() End Sub
That is all that is required to start up a new thread, assuming that you already have structured your code so that there is a subroutine/method available with no parameters representing the desired background processing. There is a problem with the code the way it stands (as discussed in the next section), but it should run at this point and should even produce the desired UI responsive as it fills in the list from the background.