简介
多线程迟早是我们要面对的一个东西,本文向你展示了一个简单的 使用两个线程来拷贝文件的应用程序
Backup将一个目录中文件拷贝到另一个目录中,它使用了一个线程来显示 正被拷贝的文件名称,另一个线程用来在拷贝的文件的同时统计文件数目和文件夹数目。这就意味着在拷贝可以开始之前不用浪费时间去等待文件数目的统计完成, 我们使用了两个线程同时完成拷贝和统计工作。
Backup对于大数量文件的拷贝也是快速和有效率的。它很快是因为当目标 文件已经存在并且没有改变过时就不做拷贝工作,因此它对于重复拷贝来说不错,因为它只拷贝新文件或更新过的文件。
这个程序的另一个特点是当在一个特点文件上发生安全性错误或其 他类型错误(当然不包括硬件错误),它不会停止拷贝工作,它将会记录下错误信息并继续完成工作。过后你可以去查看日志文件,它会告诉你发生了什么错误。大 部分错误都是由于安全配置问题产生的。
背景知识
为什么要使用多线程呢?一个原因可能一个窗口在忙碌时,你想能 点击窗口上的一个按钮。另一个原因是多核时代已经来临,硬件和操作系统级别的多任务也存在,线程无可避免,尤其当我们关注性能时。
好的,你已经决定你想在.Net中使用多线程。你可以使用BackgroundWorker,
不过我假定你应该从System.Threading
开始,并直接使用Thread
类。个人看来,它更容易使用,并且更具灵活性。
那么线程到底是什么呢?它就好比于一个源自于你的主程序的另一 个完全分离的程序。一旦线程启动,主程序对线程完全不知道。它们生死自控。要启动一个或两个线程,你可能想知道如何创建线程,如何传递数据,如何从子线程 中回调主应用程序以及主应用程序如何知道是哪个子线程回调了它?下面的代码片段会回答这些问题的。
最后一点,作者将一个类实例的方法赋予了一个线程,尽管可以将 主程序的方法赋予线程,但作者认为这样做更好。
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
' Declare first thread variable. This will be used to copy the files.
Private CopyThread As Thread '拷贝线程
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
' Delclare second thread variable. This will be used to count the folders and files.
Private CountThread As Thread '统计线程
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
统计线程一般在拷贝线程前面完成,除非你拷贝的文件数目很小, 那种情况下一切都发生的很快。
另一方面,统计线程结束后,总文件数目会被统计出来,从而用来 设置ProgressBar1.Max
属性,
下面是启动拷贝线程和统计线程的代码:
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
Private Sub StartCopy_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles StartCopy.Click
'开始拷贝
' Validate from and to folders
If Not SetRootPath Then Exit Sub
If Not CheckFromPath() Then Exit Sub
If Not CheckToPath() Then Exit Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create an instance of the copy class that will be assigned to the first thread.
Dim FileCopy As New CopyClass(Me)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Set required properties
FileCopy.FromPath = FromPathTextbox.Text
FileCopy.ToPath = ToPathTextbox.Text & _rootDir
FileCopy.StartDateTime = DateTime.Now
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Save log file name
_logFile = FileCopy.LogFileName
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create the thread and assign the class instance and method to execute
' (CopyFiles in this case) when the thread is started.
CopyThread = New Thread(AddressOf FileCopy.CopyFiles) '拷贝线程
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Start the first thread to copy the files.
CopyThread.Name = "Copy"
CopyThread.IsBackground = True
CopyThread.Start()
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create another instance of the copy class that will be assigned to the second thread.
Dim FileFolderCount As New CopyClass(Me)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Set required properties
FileFolderCount.FromPath = FromPathTextbox.Text
FileFolderCount.ToPath = ToPathTextbox.Text
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create the thread and assign the class instance and method to execute
' (CopyFiles in this case) when the thread is started.
CountThread = New Thread(AddressOf FileFolderCount.GetCountData) '计数线程
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Start the second thread to count folders and files while the copy is running at the same time.
CountThread.Name = "Count"
CountThread.IsBackground = True
CountThread.Start()
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Reset form controls
StartCopy.Enabled = False
Panel1.Enabled = False
StopCopy.Enabled = True
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
下面是终止两个线程的代码:
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
Private Sub StopCopy_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles StopCopy.Click
'终止线程
If CopyThread.IsAlive Then CopyThread.Abort()
If CountThread.IsAlive Then CountThread.Abort()
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
主界面的两个delegate方法,用来响应子线程的回调,刷新主界面
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
Public Sub CopyThreadMessage(ByVal ThreadName As String, ByVal CopiedFiles As Long, ByVal Message As String)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' If finished copying
If Message = "END" Then
lblStatus.Text = "Status: Copy Finsihed. Copied " + _totalFiles.ToString + " files in " + _totalFolders.ToString + " folders."
txtFile.Text = "Copy completed successfully."
ProgressBar1.Value = ProgressBar1.Maximum
CopyThread.Abort()
CountThread.Abort()
Exit Sub
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Show current file
txtFile.Text = "Copying: " & Message
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Update progressbar
If ProgressBar1.Maximum <> 0 Then ProgressBar1.Value = _totalFiles - (_totalFiles - CopiedFiles)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Update status (TotalFiles not zero means counting has finished)
If _totalFiles <> 0 Then
lblStatus.Text = "Status: Copying. There are " + _totalFiles.ToString + " files in " + _totalFolders.ToString + " folders. Files copied so far " & CopiedFiles & "."
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Save for CountThreadMessage()
_copiedFiles = CopiedFiles
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
Public Sub CountThreadMessage(ByVal ThreadName As String, ByVal Files As Long, ByVal TotalFiles As Long, ByVal Folders As Long, ByVal Message As String)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Display current count
lblStatus.Text = "Status: Copying and Counting. So far there are " + Files.ToString + " files in " + Folders.ToString + " folders."
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Save totals when finished counting for CopyThreadMessage()
If Message = "END" Then
_totalFiles = TotalFiles
_totalFolders = Folders
lblStatus.Text = "Status: Copying. There are " + _totalFiles.ToString + " files in " + _totalFolders.ToString + " folders. Files copied so far " & _copiedFiles & "."
ProgressBar1.Maximum = _totalFiles
ProgressBar1.Value = _totalFiles - (_totalFiles - _copiedFiles)
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
负责拷贝和统计的类:
Imports System.Threading
Imports System.IO
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
Public Class CopyClass
'This will hold the reference to the client form
Private _clientApp As Form
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Create a delegate method that will map to the CopyThreadMessage method of the client app
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Delegate Sub CallClientCopy(ByVal ThreadName As String, ByVal FilesRemaining As Long, ByVal Message As String)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Create a delegate method that will map to the CountThreadMessage method of the client app
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Delegate Sub CallClientCount(ByVal ThreadName As String, ByVal TotalFiles As Long, ByVal TotalFolders As Long, ByVal Files As Long, ByVal Message As String)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Create an object for each deletegate
Private _callClientCopy As CallClientCopy
Private _callClientCount As CallClientCount
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Property variables
Private _firstTime As Boolean
Private _fromPath As String
Private _toPath As String
Private _directories As Long
Private _files As Long
Private _copiedFiles As Long
Private _totalFiles As Long
Private _fileName As String
Private _logFile As StreamWriter
Private _startDateTime As Date
Private _logFileName As String
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Constants
Private Const LOG_FILE As String = "BackupLog.txt"
Private Const ERR_MSG As String = "Error accessing file: "
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public Sub New(ByRef ClientApp As Backup)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Save the reference to the client app
_clientApp = ClientApp
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Assign delegate objects
_callClientCopy = AddressOf ClientApp.CopyThreadMessage
_callClientCount = AddressOf ClientApp.CountThreadMessage
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public Sub CopyFiles()
'Do the work of the first thread here
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Give this thread a name
If Thread.CurrentThread.Name = Nothing Then Thread.CurrentThread.Name = "Copy"
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create a new DirectoryInfo object for from path.
Dim dir As New DirectoryInfo(FromPath)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Call the GetFileSystemInfos method.
Dim FSinfo As FileSystemInfo() = dir.GetFileSystemInfos
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Open log file
OpenLog()
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Copy one file at a time looping until all files are copied
ReallyCopyFiles(FSinfo)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
WriteLog("Copy completed successfully.")
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Call client one last time to signal end of copy
CallClient(Thread.CurrentThread.Name, _copiedFiles, _totalFiles, _directories, "END")
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public Sub GetCountData()
'Do the work of the second thread here
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Give this thread a name
If Thread.CurrentThread.Name = Nothing Then Thread.CurrentThread.Name = "Count"
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create a new DirectoryInfo object for from path.
Dim dir As New DirectoryInfo(FromPath)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Call the GetFileSystemInfos method.
Dim FSinfo As FileSystemInfo() = dir.GetFileSystemInfos
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Count folder and files
CountFiles(FSinfo)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Save total files count
_totalFiles = _files
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Send message to client form
CallClient(Thread.CurrentThread.Name, _files, _totalFiles, _directories, "END")
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Sub ReallyCopyFiles(ByVal FSInfo As FileSystemInfo())
' Check the FSInfo parameter.
If FSInfo Is Nothing Then
Throw New ArgumentNullException("FSInfo")
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Iterate through each item.
Dim i As FileSystemInfo
For Each i In FSInfo
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
Try
' Check to see if this is a DirectoryInfo object.
If TypeOf i Is DirectoryInfo Then
' Cast the object to a DirectoryInfo object.
Dim dInfo As DirectoryInfo = CType(i, DirectoryInfo)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Iterate (recurse) through all sub-directories.
ReallyCopyFiles(dInfo.GetFileSystemInfos())
' Check to see if this is a FileInfo object.
ElseIf TypeOf i Is FileInfo Then
'save the full path and file name
_fileName = i.FullName
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Get the copy path name only
Dim copypath As String = ToPath & Mid(_fileName, Len(FromPath) + 1, Len(_fileName) - Len(FromPath) - Len(i.Name))
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Create copy path if it does not exist
If Not Directory.Exists(copypath) Then
Directory.CreateDirectory(copypath)
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Get the to path and filename
Dim tofile As String = ToPath & Mid(_fileName, Len(FromPath) + 1)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Update status info on client
Dim fi As New FileInfo(_fileName)
Dim Message As String = _fileName & " is " & Decimal.Round(CDec(fi.Length / 1048576), 2) & "MB in length."
CallClient(Thread.CurrentThread.Name, _copiedFiles, _totalFiles, _directories, Message)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' if file exists check if file has been updated since last copy
Dim OkayToCopy As Boolean = True
If File.Exists(tofile) Then
If File.GetLastWriteTime(_fileName) = File.GetLastWriteTime(tofile) Then
OkayToCopy = False
End If
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Copy file with overwrite
If OkayToCopy Then File.Copy(_fileName, tofile, True)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Increment copied file count
_copiedFiles += 1
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End If
Catch ex As Exception
' Report error but continue processing
WriteLog(ERR_MSG & _fileName & vbCrLf & ex.Message.ToString)
End Try
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
Next i
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Sub CountFiles(ByVal FSInfo As FileSystemInfo())
Static ShowCount As Long = 0
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Check the FSInfo parameter.
If FSInfo Is Nothing Then
Throw New ArgumentNullException("FSInfo")
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Iterate through each item.
Dim i As FileSystemInfo
For Each i In FSInfo
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
Try
' Check to see if this is a DirectoryInfo object.
If TypeOf i Is DirectoryInfo Then
' Add one to the directory count.
_directories += 1
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Cast the object to a DirectoryInfo object.
Dim dInfo As DirectoryInfo = CType(i, DirectoryInfo)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Iterate (recurse) through all sub-directories.
CountFiles(dInfo.GetFileSystemInfos())
' Check to see if this is a FileInfo object.
ElseIf TypeOf i Is FileInfo Then
' Add one to the file count.
_files += 1
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'display count for first file in every folder then every 200 - for faster performance
Select Case ShowCount
Case 0
' Display count
CallClient(Thread.CurrentThread.Name, _files, _totalFiles, _directories, "")
Case Is >= 200
' Display count
CallClient(Thread.CurrentThread.Name, _files, _totalFiles, _directories, "")
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'reset so display is every 200 files in folder
ShowCount = 0
End Select
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Increment show count
ShowCount += 1
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End If
Catch ex As Exception
'Record error then continue (like a resume next)
WriteLog(ERR_MSG & _fileName & vbCrLf & ex.Message.ToString)
End Try
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
Next i
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Sub CallClient(ByVal ThreadName As String, ByVal Files As Long, ByVal TotalFiles As Long, ByVal Directories As Long, ByVal Message As String)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
Select Case ThreadName
Case "Copy"
'Call the delegated method
_clientApp.Invoke(_callClientCopy, ThreadName, Files, Message)
Case "Count"
'Call the delegated method
_clientApp.Invoke(_callClientCount, ThreadName, Files, TotalFiles, Directories, Message)
End Select
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Let the thread sleep before continuing so the client app will have time to be process (1 millisecond is enough)
Thread.Sleep(0)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Sub OpenLog()
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create log file
If Not File.Exists(StartDateTime & "-" & LOG_FILE) Then
Using _logFile As StreamWriter = File.CreateText(LogFileName)
_logFile.WriteLine("Logfile name is: " & LogFileName)
_logFile.WriteLine("BACKUP LOG FILE STARTED AT: " & StartDateTime.ToString)
_logFile.WriteLine("================================================")
_logFile.Write("Copying FROM: " & _fromPath)
_logFile.WriteLine()
_logFile.Write("Copying TO: " & _toPath)
_logFile.WriteLine()
_logFile.Close()
End Using
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Sub WriteLog(ByVal Message As String)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create an instance of StreamWriter to write text to a file.
Using _logFile As StreamWriter = File.AppendText(LogFileName)
' Add some text to the file.
_logFile.WriteLine()
_logFile.WriteLine("TIME OF LOG ENTRY: " & DateTime.Now)
' Arbitrary objects can also be written to the file.
_logFile.WriteLine(Message)
_logFile.Flush()
_logFile.Close()
End Using
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Sub CloseLog()
If File.Exists(LogFileName) Then _logFile.Close()
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Property FirstTime() As Boolean
Get
Return _firstTime
End Get
Set(ByVal value As Boolean)
_firstTime = value
End Set
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public Property FromPath() As String
Get
Return _fromPath
End Get
Set(ByVal value As String)
_fromPath = value
End Set
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public Property ToPath() As String
Get
Return _toPath
End Get
Set(ByVal value As String)
_toPath = value
End Set
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public Property StartDateTime() As Date
Get
Return _startDateTime
End Get
Set(ByVal value As Date)
_startDateTime = value
End Set
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public ReadOnly Property LogFileName() As String
Get
Return Format(StartDateTime, "yyMMdd-hhmmss") & "-" & LOG_FILE
End Get
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public ReadOnly Property Directories() As Long
Get
Return _directories
End Get
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public ReadOnly Property Files() As Long
Get
Return _files
End Get
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public ReadOnly Property TotalFiles() As Long
Get
Return _totalFiles
End Get
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Class
简介
多线程迟早是我们要面对的一个东西,本文向你展示了一个简单的 使用两个线程来拷贝文件的应用程序
Backup将一个目录中文件拷贝到另一个目录中,它使用了一个线程来显示 正被拷贝的文件名称,另一个线程用来在拷贝的文件的同时统计文件数目和文件夹数目。这就意味着在拷贝可以开始之前不用浪费时间去等待文件数目的统计完成, 我们使用了两个线程同时完成拷贝和统计工作。
Backup对于大数量文件的拷贝也是快速和有效率的。它很快是因为当目标 文件已经存在并且没有改变过时就不做拷贝工作,因此它对于重复拷贝来说不错,因为它只拷贝新文件或更新过的文件。
这个程序的另一个特点是当在一个特点文件上发生安全性错误或其 他类型错误(当然不包括硬件错误),它不会停止拷贝工作,它将会记录下错误信息并继续完成工作。过后你可以去查看日志文件,它会告诉你发生了什么错误。大 部分错误都是由于安全配置问题产生的。
背景知识
为什么要使用多线程呢?一个原因可能一个窗口在忙碌时,你想能 点击窗口上的一个按钮。另一个原因是多核时代已经来临,硬件和操作系统级别的多任务也存在,线程无可避免,尤其当我们关注性能时。
好的,你已经决定你想在.Net中使用多线程。你可以使用BackgroundWorker,
不过我假定你应该从System.Threading
开始,并直接使用Thread
类。个人看来,它更容易使用,并且更具灵活性。
那么线程到底是什么呢?它就好比于一个源自于你的主程序的另一 个完全分离的程序。一旦线程启动,主程序对线程完全不知道。它们生死自控。要启动一个或两个线程,你可能想知道如何创建线程,如何传递数据,如何从子线程 中回调主应用程序以及主应用程序如何知道是哪个子线程回调了它?下面的代码片段会回答这些问题的。
最后一点,作者将一个类实例的方法赋予了一个线程,尽管可以将 主程序的方法赋予线程,但作者认为这样做更好。
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
' Declare first thread variable. This will be used to copy the files.
Private CopyThread As Thread '拷贝线程
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
' Delclare second thread variable. This will be used to count the folders and files.
Private CountThread As Thread '统计线程
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
统计线程一般在拷贝线程前面完成,除非你拷贝的文件数目很小, 那种情况下一切都发生的很快。
另一方面,统计线程结束后,总文件数目会被统计出来,从而用来 设置ProgressBar1.Max
属性,
下面是启动拷贝线程和统计线程的代码:
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
Private Sub StartCopy_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles StartCopy.Click
'开始拷贝
' Validate from and to folders
If Not SetRootPath Then Exit Sub
If Not CheckFromPath() Then Exit Sub
If Not CheckToPath() Then Exit Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create an instance of the copy class that will be assigned to the first thread.
Dim FileCopy As New CopyClass(Me)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Set required properties
FileCopy.FromPath = FromPathTextbox.Text
FileCopy.ToPath = ToPathTextbox.Text & _rootDir
FileCopy.StartDateTime = DateTime.Now
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Save log file name
_logFile = FileCopy.LogFileName
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create the thread and assign the class instance and method to execute
' (CopyFiles in this case) when the thread is started.
CopyThread = New Thread(AddressOf FileCopy.CopyFiles) '拷贝线程
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Start the first thread to copy the files.
CopyThread.Name = "Copy"
CopyThread.IsBackground = True
CopyThread.Start()
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create another instance of the copy class that will be assigned to the second thread.
Dim FileFolderCount As New CopyClass(Me)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Set required properties
FileFolderCount.FromPath = FromPathTextbox.Text
FileFolderCount.ToPath = ToPathTextbox.Text
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create the thread and assign the class instance and method to execute
' (CopyFiles in this case) when the thread is started.
CountThread = New Thread(AddressOf FileFolderCount.GetCountData) '计数线程
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Start the second thread to count folders and files while the copy is running at the same time.
CountThread.Name = "Count"
CountThread.IsBackground = True
CountThread.Start()
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Reset form controls
StartCopy.Enabled = False
Panel1.Enabled = False
StopCopy.Enabled = True
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
下面是终止两个线程的代码:
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
Private Sub StopCopy_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles StopCopy.Click
'终止线程
If CopyThread.IsAlive Then CopyThread.Abort()
If CountThread.IsAlive Then CountThread.Abort()
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
主界面的两个delegate方法,用来响应子线程的回调,刷新主界面
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
Public Sub CopyThreadMessage(ByVal ThreadName As String, ByVal CopiedFiles As Long, ByVal Message As String)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' If finished copying
If Message = "END" Then
lblStatus.Text = "Status: Copy Finsihed. Copied " + _totalFiles.ToString + " files in " + _totalFolders.ToString + " folders."
txtFile.Text = "Copy completed successfully."
ProgressBar1.Value = ProgressBar1.Maximum
CopyThread.Abort()
CountThread.Abort()
Exit Sub
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Show current file
txtFile.Text = "Copying: " & Message
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Update progressbar
If ProgressBar1.Maximum <> 0 Then ProgressBar1.Value = _totalFiles - (_totalFiles - CopiedFiles)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Update status (TotalFiles not zero means counting has finished)
If _totalFiles <> 0 Then
lblStatus.Text = "Status: Copying. There are " + _totalFiles.ToString + " files in " + _totalFolders.ToString + " folders. Files copied so far " & CopiedFiles & "."
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Save for CountThreadMessage()
_copiedFiles = CopiedFiles
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
Public Sub CountThreadMessage(ByVal ThreadName As String, ByVal Files As Long, ByVal TotalFiles As Long, ByVal Folders As Long, ByVal Message As String)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Display current count
lblStatus.Text = "Status: Copying and Counting. So far there are " + Files.ToString + " files in " + Folders.ToString + " folders."
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Save totals when finished counting for CopyThreadMessage()
If Message = "END" Then
_totalFiles = TotalFiles
_totalFolders = Folders
lblStatus.Text = "Status: Copying. There are " + _totalFiles.ToString + " files in " + _totalFolders.ToString + " folders. Files copied so far " & _copiedFiles & "."
ProgressBar1.Maximum = _totalFiles
ProgressBar1.Value = _totalFiles - (_totalFiles - _copiedFiles)
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
负责拷贝和统计的类:
Imports System.Threading
Imports System.IO
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
Public Class CopyClass
'This will hold the reference to the client form
Private _clientApp As Form
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Create a delegate method that will map to the CopyThreadMessage method of the client app
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Delegate Sub CallClientCopy(ByVal ThreadName As String, ByVal FilesRemaining As Long, ByVal Message As String)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Create a delegate method that will map to the CountThreadMessage method of the client app
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Delegate Sub CallClientCount(ByVal ThreadName As String, ByVal TotalFiles As Long, ByVal TotalFolders As Long, ByVal Files As Long, ByVal Message As String)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Create an object for each deletegate
Private _callClientCopy As CallClientCopy
Private _callClientCount As CallClientCount
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Property variables
Private _firstTime As Boolean
Private _fromPath As String
Private _toPath As String
Private _directories As Long
Private _files As Long
Private _copiedFiles As Long
Private _totalFiles As Long
Private _fileName As String
Private _logFile As StreamWriter
Private _startDateTime As Date
Private _logFileName As String
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Constants
Private Const LOG_FILE As String = "BackupLog.txt"
Private Const ERR_MSG As String = "Error accessing file: "
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public Sub New(ByRef ClientApp As Backup)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Save the reference to the client app
_clientApp = ClientApp
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Assign delegate objects
_callClientCopy = AddressOf ClientApp.CopyThreadMessage
_callClientCount = AddressOf ClientApp.CountThreadMessage
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public Sub CopyFiles()
'Do the work of the first thread here
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Give this thread a name
If Thread.CurrentThread.Name = Nothing Then Thread.CurrentThread.Name = "Copy"
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create a new DirectoryInfo object for from path.
Dim dir As New DirectoryInfo(FromPath)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Call the GetFileSystemInfos method.
Dim FSinfo As FileSystemInfo() = dir.GetFileSystemInfos
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Open log file
OpenLog()
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Copy one file at a time looping until all files are copied
ReallyCopyFiles(FSinfo)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
WriteLog("Copy completed successfully.")
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Call client one last time to signal end of copy
CallClient(Thread.CurrentThread.Name, _copiedFiles, _totalFiles, _directories, "END")
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public Sub GetCountData()
'Do the work of the second thread here
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Give this thread a name
If Thread.CurrentThread.Name = Nothing Then Thread.CurrentThread.Name = "Count"
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create a new DirectoryInfo object for from path.
Dim dir As New DirectoryInfo(FromPath)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Call the GetFileSystemInfos method.
Dim FSinfo As FileSystemInfo() = dir.GetFileSystemInfos
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Count folder and files
CountFiles(FSinfo)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Save total files count
_totalFiles = _files
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Send message to client form
CallClient(Thread.CurrentThread.Name, _files, _totalFiles, _directories, "END")
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Sub ReallyCopyFiles(ByVal FSInfo As FileSystemInfo())
' Check the FSInfo parameter.
If FSInfo Is Nothing Then
Throw New ArgumentNullException("FSInfo")
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Iterate through each item.
Dim i As FileSystemInfo
For Each i In FSInfo
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
Try
' Check to see if this is a DirectoryInfo object.
If TypeOf i Is DirectoryInfo Then
' Cast the object to a DirectoryInfo object.
Dim dInfo As DirectoryInfo = CType(i, DirectoryInfo)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Iterate (recurse) through all sub-directories.
ReallyCopyFiles(dInfo.GetFileSystemInfos())
' Check to see if this is a FileInfo object.
ElseIf TypeOf i Is FileInfo Then
'save the full path and file name
_fileName = i.FullName
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Get the copy path name only
Dim copypath As String = ToPath & Mid(_fileName, Len(FromPath) + 1, Len(_fileName) - Len(FromPath) - Len(i.Name))
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Create copy path if it does not exist
If Not Directory.Exists(copypath) Then
Directory.CreateDirectory(copypath)
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Get the to path and filename
Dim tofile As String = ToPath & Mid(_fileName, Len(FromPath) + 1)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Update status info on client
Dim fi As New FileInfo(_fileName)
Dim Message As String = _fileName & " is " & Decimal.Round(CDec(fi.Length / 1048576), 2) & "MB in length."
CallClient(Thread.CurrentThread.Name, _copiedFiles, _totalFiles, _directories, Message)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' if file exists check if file has been updated since last copy
Dim OkayToCopy As Boolean = True
If File.Exists(tofile) Then
If File.GetLastWriteTime(_fileName) = File.GetLastWriteTime(tofile) Then
OkayToCopy = False
End If
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Copy file with overwrite
If OkayToCopy Then File.Copy(_fileName, tofile, True)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Increment copied file count
_copiedFiles += 1
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End If
Catch ex As Exception
' Report error but continue processing
WriteLog(ERR_MSG & _fileName & vbCrLf & ex.Message.ToString)
End Try
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
Next i
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Sub CountFiles(ByVal FSInfo As FileSystemInfo())
Static ShowCount As Long = 0
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Check the FSInfo parameter.
If FSInfo Is Nothing Then
Throw New ArgumentNullException("FSInfo")
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Iterate through each item.
Dim i As FileSystemInfo
For Each i In FSInfo
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
Try
' Check to see if this is a DirectoryInfo object.
If TypeOf i Is DirectoryInfo Then
' Add one to the directory count.
_directories += 1
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Cast the object to a DirectoryInfo object.
Dim dInfo As DirectoryInfo = CType(i, DirectoryInfo)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Iterate (recurse) through all sub-directories.
CountFiles(dInfo.GetFileSystemInfos())
' Check to see if this is a FileInfo object.
ElseIf TypeOf i Is FileInfo Then
' Add one to the file count.
_files += 1
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'display count for first file in every folder then every 200 - for faster performance
Select Case ShowCount
Case 0
' Display count
CallClient(Thread.CurrentThread.Name, _files, _totalFiles, _directories, "")
Case Is >= 200
' Display count
CallClient(Thread.CurrentThread.Name, _files, _totalFiles, _directories, "")
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'reset so display is every 200 files in folder
ShowCount = 0
End Select
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Increment show count
ShowCount += 1
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End If
Catch ex As Exception
'Record error then continue (like a resume next)
WriteLog(ERR_MSG & _fileName & vbCrLf & ex.Message.ToString)
End Try
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
Next i
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Sub CallClient(ByVal ThreadName As String, ByVal Files As Long, ByVal TotalFiles As Long, ByVal Directories As Long, ByVal Message As String)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
Select Case ThreadName
Case "Copy"
'Call the delegated method
_clientApp.Invoke(_callClientCopy, ThreadName, Files, Message)
Case "Count"
'Call the delegated method
_clientApp.Invoke(_callClientCount, ThreadName, Files, TotalFiles, Directories, Message)
End Select
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
'Let the thread sleep before continuing so the client app will have time to be process (1 millisecond is enough)
Thread.Sleep(0)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Sub OpenLog()
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create log file
If Not File.Exists(StartDateTime & "-" & LOG_FILE) Then
Using _logFile As StreamWriter = File.CreateText(LogFileName)
_logFile.WriteLine("Logfile name is: " & LogFileName)
_logFile.WriteLine("BACKUP LOG FILE STARTED AT: " & StartDateTime.ToString)
_logFile.WriteLine("================================================")
_logFile.Write("Copying FROM: " & _fromPath)
_logFile.WriteLine()
_logFile.Write("Copying TO: " & _toPath)
_logFile.WriteLine()
_logFile.Close()
End Using
End If
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Sub WriteLog(ByVal Message As String)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
' Create an instance of StreamWriter to write text to a file.
Using _logFile As StreamWriter = File.AppendText(LogFileName)
' Add some text to the file.
_logFile.WriteLine()
_logFile.WriteLine("TIME OF LOG ENTRY: " & DateTime.Now)
' Arbitrary objects can also be written to the file.
_logFile.WriteLine(Message)
_logFile.Flush()
_logFile.Close()
End Using
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Sub CloseLog()
If File.Exists(LogFileName) Then _logFile.Close()
End Sub
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Private Property FirstTime() As Boolean
Get
Return _firstTime
End Get
Set(ByVal value As Boolean)
_firstTime = value
End Set
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public Property FromPath() As String
Get
Return _fromPath
End Get
Set(ByVal value As String)
_fromPath = value
End Set
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public Property ToPath() As String
Get
Return _toPath
End Get
Set(ByVal value As String)
_toPath = value
End Set
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public Property StartDateTime() As Date
Get
Return _startDateTime
End Get
Set(ByVal value As Date)
_startDateTime = value
End Set
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public ReadOnly Property LogFileName() As String
Get
Return Format(StartDateTime, "yyMMdd-hhmmss") & "-" & LOG_FILE
End Get
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public ReadOnly Property Directories() As Long
Get
Return _directories
End Get
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public ReadOnly Property Files() As Long
Get
Return _files
End Get
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Public ReadOnly Property TotalFiles() As Long
Get
Return _totalFiles
End Get
End Property
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
End Class