Home > Articles > Programming > Windows Programming

  • Print
  • + Share This
From the author of

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

NOTE

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.

  • + Share This
  • 🔖 Save To Your Account