#### Tagged as

, 10 Jul 2008 CPOL
 Rate this:
Multithreading is something we will all have to deal with sooner or later. This relatively simple application shows you how to use two threads to copy files. It is also a very handy Windows backup utility with a mirror feature, a batch mode feature, a log file feature and a help button!

## Introduction

Backup is a utility program that will copy files from one path to another using one thread to display the names of the files being copied, and another thread to count the number of files and folders at the same time the files are being copied. This means no time is wasted waiting to get the file count before the copy can start; it copies and counts at the same time using two threads.

The Backup application is also a handy utility to copy large volumes of files from one path to another quickly and efficiently. It is fast simply because it will not copy a file that is already in the destination path and has not changed (why copy a file that is already there and hasn't changed?). So it's great for repetitive backups as it only copies new or updated files.

Another added feature is that Backup will not stop copying just because it got a security error on a particular file, or any type of error for that matter (excluding a hardware failure, of course). It will simply log the error and keep going until finished. You can then review the errors in the log file afterwards, which will show the error and the file that failed to copy. Most failures are due to security configuration problems.

## Background

If you just want the Backup utility, then there is no need to read on as the rest of this article is a technical discussion on multithreading or threading. The download links are at the top left corner of this page.

So why use threading? One reason may be because you want to be able to press a button on the form while the form is busy but it won't let you because it's busy. The old VB6 way of doing this is to use a Timer perhaps, or use Do Events at regular points in the code while processing. I doubt that Do Events will work for this purpose in VB.NET and although a Timer may solve the problem, there are other reasons to get familiar with threading.

Another reason is that with dual/multi core processing now available and true multitasking at the hardware and operating system level, threading is going to be hard to avoid, especially when performance is a concern and your application is processor intensive.

Okay, so you have decided you want to do threading in .NET. You could use the BackgroundWorker (Windows Form control), but I suspect you should start with the System.Threading namespace and use the Thread class directly. My personal view is that the Thread class is easier to use and gives you more flexibility although the BackgroundWorker will remove some of the grunt programming work.

So what is a thread anyway? Simply put, it is the equivalent to another completely separate program that is initiated from your main program. Once a thread is started, the main program does not know anything about the thread (or threads); they are effectively separate programs in their own right. So some of the first things you may want to know are how to create them, how to pass data between them, how to call the main application from a thread and finally, how does the main application know which thread has called it?

Viewing the code snippets below should help answer these questions.

One final point before we start, using threads to copy files is not necessarily the best application of multithreading as copying files is disk I/O intensive rather than processor intensive. But it's still a fair use as counting files and copying files can occur asynchronously rather than synchronously, so why wait? And besides that, I think Backup is a simple and handy utility at the same time.

## Using the Code

This description of the code will not discuss the finer points of how to copy files but simply show the relevant source code to create the threads and use them from the main form application; showing how the threads are created and how parameters can be passed from the threads to the main Windows forms application.

First, take a quick look at the imported namespaces in the Backup form:

Imports System.Threading
Imports System.IO
Imports System.Diagnostics.Process

The System.Threading namespace is imported so you can create a Thread class for each thread. The System.IO namespace for file operations and the System.Diagnostics.Process is used to view the log file with Notepad.

The next code snippet from the Backup utility shows the declaration of a FileCopy class called CopyFiles in the main Windows form:

Imports System.Windows

Public Class Backup

' Declare the FileCopy class.
' This class will create 3 threads to copy, count and mirror files
' and raise events for each, so the events must be handled
' to update the form with status data.
Dim WithEvents CopyFiles As FileCopy

Private Sub StartCopy_Click(ByVal sender As System.Object, e As System.EventArgs) _
Handles StartCopy.Click

' Create the FileCopy class which will initiate the threads
CopyFiles = New FileCopy

' Initiate the copy, count and mirror threads from the FileCopy class
CopyFiles.StartCopy()

End Sub
.
. remaining forms code
.
End Class

Pressing the Go button on the Backup form will initiate the StartCopy_Click subroutine. This subroutine will instantiate the FileCopy class as CopyFiles and then use the CopyFiles.StartCopy() method to begin the copy.

So far, all we have done in the main Windows form is declare as class, instantiate it, and then execute the StartCopy() method. So why have we created a class? Is it just good OOD practice?

And where is the code that declares the threads and starts them? Well, the very important point I am trying to make here, is that if you want to use threads, then it's best to create a class and use that class to start the threads. This is because the class can raise events whenever the threads it initiates need to communicate with the parent thread.

This might seem obvious for the well rehearsed (and the arrogant) , but it's a very important design consideration, i.e. use a class to start threads.

Okay, now let's look at the FileCopy class, and see how it starts the threads:

Imports System.IO

Public Class FileCopy

' Declares the variables you will use to hold your thread objects.

Public Sub StartCopy()

' Sets the copy and count threads using the AddressOf the subroutine where

End Sub
.
. code for the rest of the class
.
End Class

Pretty simple huh? Declare the threads and then have a method start them. So far, so good. But now that we have started the CopyThread and the CountThread, how do we get them to "talk to" the Backup form by passing parameters? How will the Backup form know which thread is doing what? These are reasonable questions when you consider threads run in their own address space and don't know about each other.

If you note the use of the AddressOf operator when instantiating the threads, you will see that each thread is assigned to a private subroutine in the FileCopy class called Copy and Count, respectively. All you really need to do is have your code raise events in these subroutines whenever they want to communicate with the main parent thread (the parent thread is the Backup windows form control/class).

Let's look quickly by expanding the FileCopy class and see how this is done:

Imports System.IO

Public Class FileCopy

' Declare the events that will be raised by each thread
Public Event CopyStatus(ByVal sender As Object, ByVal e As BackupEventArgs)
Public Event CountStatus(ByVal sender As Object, ByVal e As BackupEventArgs)
Public Event MirrorStatus(ByVal sender As Object, ByVal e As BackupEventArgs)

' Declares the variables you will use to hold your thread objects.

' Class variables' Class variables
Private _filePath As String
Private _fileSize As String
Private _copiedFolders As Long
Private _copiedFiles As Long
Private _countedFolders As Long
Private _countedFiles As Long
Private _mirroredFolders As Long
Private _mirroredFiles As Long
.
. even more class variables but we will just show the relevant ones.
.

Public Sub StartCopy()

' Sets the copy and count threads using the AddressOf the subroutine where

End Sub

Private Sub Copy()
.
. this is a program loop with logic to copy files
.
Loop to Copy Files

Copy a file here

' Raise the copy status event at the end of the program loop
RaiseEvent CopyStatus(Me, New BackupEventArgs_
("", 0, _copiedFiles, _copiedFolders))
End Loop

End Sub

Private Sub Count()
.
. this is a program loop with logic to count files
.
Loop to Count Files

Count a file here

' Raise the count status event at the end of the program loop
RaiseEvent CountStatus(Me, New BackupEventArgs_
("", 0, _countedFiles, _countedFolders))
End Loop

End Sub
.
. code for the rest of the class
.
End Class

So, there you have it. A basic class declaration that starts threads and raises events whenever it needs to communicate with the main parent thread, (which we know is the Backup Windows form control).

Note the use of Threading.Thread.Sleep. This is needed to allow the parent thread time to do its thing, like refresh the form display and respond to button clicks. Pretty cool so far, but we are not quite there yet.

We now need to look at the main parent thread (the Backup Windows form control) and see the code needed to pass those parameters.

First, take note the e parameter in the declaration for the events in the code snippet above. The e parameter is a variable of a custom class called BackupEventArgs that has four parameters called FilePath, FileSize, FileCount and FolderCount. This custom class inherits from EventArgs and is just a standard way to declare parameters for events. You could have just as easily declared a separate variable for each of the parameters instead of making a custom class to contain the parameters. To see what the BackupEventArgs class is all about, please refer to the source code, as how you declare parameters for events is a little outside the scope of this article.

For now, just consider that the e variable contains the parameters (FilePath, FileSize, FileCount and FolderCount) that we want to pass to and process in the main parent thread.

So, let's focus back on the parent thread, and let's see the code that deals with the events:

Imports System.Windows

Public Class Backup

' Declare the FileCopy class. This class will create 3 threads to copy,
' count and mirror files
' and raise events for each, so the events must be handled to update
' the form with status data.
Dim WithEvents CopyFiles As FileCopy

' Declare delegate handlers for the copy and count events
Public Delegate Sub CopyHandler(ByVal FilePath As String, _
ByVal FileSize As Long, ByVal FileCount As Long)
Public Delegate Sub CountHandler(ByVal FileCount As Long, ByVal FolderCount As Long)
.
. more declarations
.
Private Sub StartCopy_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles StartCopy.Click

' Create the FileCopy class which will initiate the threads
CopyFiles = New FileCopy

' Initiate the copy, count and mirror threads from the FileCopy class
CopyFiles.StartCopy()

End Sub

Private Sub CopyStatus(ByVal FilePath As String, ByVal FileSize As Long, _
ByVal FileCount As Long)
.
. some code to update the forms display
.
End Sub

Private Sub CopyFiles_CopyStatus(ByVal sender As Object, _
ByVal e As BackupEventArgs) Handles CopyFiles.CopyStatus
' BeginInvoke causes asynchronous execution to begin at the address
' specified by the delegate. Simply put, it transfers execution of
' this method back to the main thread. Any parameters required by
' the method contained at the delegate are wrapped in an object and
' passed.

New Object() {e.FilePath, e.FileSize, e.FileCount})

End Sub
.
. remaining forms code
.
End Class

The code snippet above has a CopyFiles_CopyStatus() event that is added to the main form (parent thread) when the CopyFiles variable is declared using WithEvents. It uses the Me.BeginInvoke method of the form control to pass the parameters from the CopyThread to the parent form thread. All Windows controls, including the forms control, have a BeginInvoke method for asynchronous execution, (and an Invoke method for synchronous execution), that allow you execute the specified delegate.

In the code above, the specified delegate is CopyHandler which is assigned to the CopyStatus subroutine using the AddressOf operator. When the CopyFiles_CopyStatus() event occurs, it passes the parameters back to the main parent thread (the Backup Windows form control) by executing the CopyStatus() subroutine via the delegate, asynchronously.

If I have left you confused, just consider that although it looks like the CopyFiles_CopyStatus() event is occurring in the Backup windows form control/class, it actually isn't. It's really occurring in the CopyThread thread, which is a separate bit of code in its own right that doesn't know about the parent thread. A difficult concept to grasp when the event in question is declared in the Windows form control class, but that's how it works.

Moving on, (for those still with me), just a couple of pointers that will help if you are new to threading. If we revise the FileCopy class code snippet below to include the Join method that is available on a thread, we can make one thread wait for another thread at a point in the code.

One problem (challenge actually) I had was that I needed to make the copy thread wait for the count thread to complete before it (the copy thread) terminates naturally (i.e. finishes copying files). You would expect the count thread to finish first, but when there are no files to copy, sometimes the copy thread finished first!

Using the join method allows me to deal with this situation. So remember the Join method if one thread depends on, and must wait for, the completion of another:

Imports System.IO

Public Class FileCopy

' Declare the events that will be raised by each thread
Public Event CopyStatus(ByVal sender As Object, ByVal e As BackupEventArgs)
Public Event CountStatus(ByVal sender As Object, ByVal e As BackupEventArgs)
Public Event MirrorStatus(ByVal sender As Object, ByVal e As BackupEventArgs)

' Declares the variables you will use to hold your thread objects.

' Class variables' Class variables
Private _filePath As String
Private _fileSize As String
Private _copiedFolders As Long
Private _copiedFiles As Long
Private _countedFolders As Long
Private _countedFiles As Long
Private _mirroredFolders As Long
Private _mirroredFiles As Long
.
. even more class variables but we will just show the relevant ones.
.

Public Sub StartCopy()

' Sets the copy and count threads using the AddressOf the subroutine where

End Sub

Private Sub Copy()
.
. this is a program loop with logic to copy files
.
Loop to Copy Files

Copy a file here

' Raise the copy status event at the end of the program loop
RaiseEvent CopyStatus(Me, New BackupEventArgs_
("", 0, _copiedFiles, _copiedFolders))
End Loop

' After all files have been copied, cause this CopyThread to wait

End Sub

Private Sub Count()
.
. this is a program loop with logic to count files
.
Loop to Count Files

Count a file here

' Raise the count status event at the end of the program loop
RaiseEvent CountStatus(Me, New BackupEventArgs_
("", 0, _countedFiles, _countedFolders))
End Loop

End Sub
.
. code for the rest of the class
.
End Class

And one final pointer. How do you terminate a thread? Using the Abort method is frowned upon by some, although there is some consensus that it's okay to use if aborting the whole application, some purists believe it should be deprecated for technical reasons relating to how resources are allocated and unallocated during execution. In other words, you should signal the thread to terminate itself rather than aborting it when its in the middle of something.

The code snippet below shows a method called StopThreads() in the FileCopy class that simply sets a boolean class variable called _stopNow to True. When the StopThreads() method is executed from the parent thread (Backup), all any threads have to do is check _stopNow and exit the processing loop to terminate the thread nicely, as shown in the Copy subroutine code snippet below (which has been assigned to the copy thread as we saw earlier):

Public Class FileCopy
.
. class code
.

_stopNow = True

End Sub
.
. more class code
.
.
Private Sub Copy()

While Loop to copy files

If _stopNow Then
Exit While
End If

copy a file
raise an event to notify parent thread

End While

End Sub
.
.
.
End Class

## Points of Interest

Much of the code above was developed by copying and adapting code from the MSDN. So if you really want to get into the finer points of threading, there are some very fine examples that have been well explained at msdn.microsoft.com.

I must say that the MSDN is finally improving to the point where it's hard not to be impressed. Some of the articles there even discuss best practice at length with some very fine walkthroughs. So a very special thanks to the coding gurus at MSDN.

And finally, in saying goodbye and thank you to those that have managed to read this far, please vote for my article, be generous and you will be rewarded in kind.

## History

### October 2007

Uploaded version 1.0.7 which contained bug fixes as well as added a batch mode for running without a form and processing command line parameters.

### June 2008

Uploaded version 2.0.0 which is a complete revision of the source code and this article, also added a help file and a mirror copy feature.

### July 2008

Uploaded version 2.1.0 which has a new mini status bar as well as some fixes to some faults that may surface in version 2.0.0. So please upgrade.

Also note that so far Backup has not been tested on Windows Vista. You may try it on Vista and, if there are problems, check back here periodically as there should be another release over the next few months that will be tested on Vista.

If upgrading from a previous version, please uninstall the previous version first. Do this from the Control Panel (Start/Control Panel) by double clicking the Add/Remove Programs icon and selecting Backup from the list.

#### Using the Latest Source Code

The source code for Backup version 2 was created using Visual Studio 2008 and the .NET Framework 3.5, so older versions of Visual Studio are likely to have compatibility issues.

## Share

Nice people only.
Australia
No Biography provided

 First PrevNext
 Help.pdf -- "The system cannot find the file specified" MillerME 14-Nov-13 4:39
 Filter by extension? Martin Przybysz 29-Jul-13 4:09
 Re: Filter by extension? Taner Riffat 29-Jul-13 14:38
 Log File Issue BigC24 3-Jul-13 9:43
 Re: Log File Issue Taner Riffat 4-Jul-13 16:02
 Hi BigC24   All log files are stored in "C:\Program Files (x86)\Moneysoft\Backup".   I did a quick test by using the start/run command with this in the command line:   "C:\Program Files (x86)\Moneysoft\Backup\Backup.exe" c:\from c:\to   This worked for me and produced a log file in the application directory.   Here are a few things to check:   1. Look in Task Manager and "End Task" any previous instances. Sometimes if the parameters are incorrect previous instances will still exist and should be terminated.   2. Check your parameters. Are they correct? Have a close look at the source and destination paths.   3. Try a quick test using start/run as I did above and read the log file after each test. Check the source path and destination path in the log file. Are they correct?   4. Run Backup in windows form mode and press the "Help" button. In the help PDF document there is a section on command line parameters for batch mode with examples. Look at the examples. One thing to watch out for is your path parameters must be enclosed in quotation marks if the path has spaces in it.   Let me know how you went... Regards Taner
 Re: Log File Issue BigC24 15-Aug-13 7:45
 My vote of 5 marius_enric 4-Jun-13 6:35
 My vote of 5 Jay Hyman 28-Mar-13 11:05
 Re: Need to add multi-version capability Taner Riffat 18-Aug-11 19:53
 Re: Need to add multi-version capability [modified] Mike Angelastro 18-Aug-11 21:56
 Re: Need to add multi-version capability Taner Riffat 20-Aug-11 20:46
 Re: Need to add multi-version capability [modified] Mike Angelastro 21-Aug-11 14:14
 Re: Need to add multi-version capability Taner Riffat 15-Sep-11 2:38
 My vote of 5 Member 4320844 7-May-11 12:43
 Help! ernieball_26 1-Mar-11 12:22
 Re: Help! Taner Riffat 29-May-11 16:14
 Tips Gr1mM R34p3R 2-Sep-09 4:56
 large file copies - progress reporting newcomps 16-May-09 13:52
 Re: large file copies - progress reporting [modified] Taner Riffat 17-May-09 19:22
 Vista Compatibility Taner Riffat 15-May-09 13:41
 Help me mohanshanthi 15-Feb-09 22:57
 Re: Help me Taner Riffat 16-Feb-09 0:13
 Re: Help me mohanshanthi 16-Feb-09 18:48
 Last Visit: 31-Dec-99 19:00     Last Update: 30-Jan-15 18:29 Refresh 123 Next »