多线程文件备份(VB.NET版)
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
简介
多线程迟早是我们要面对的一个东西,本文向你展示了一个简单的使用两个线程来拷贝文件的应用程序
Backup将一个目录中文件拷贝到另一个目录中,它使用了一个线程来显示正被拷贝的文件名称,另一个线程用来在拷贝的文件的同时统计文件数目和文件夹数目。这就意味着在拷贝可以开始之前不用浪费时间去等待文件数目的统计完成,我们使用了两个线程同时完成拷贝和统计工作。
Backup对于大数量文件的拷贝也是快速和有效率的。它很快是因为当目标文件已经存在并且没有改变过时就不做拷贝工作,因此它对于重复拷贝来说不错,因为它只拷贝新文件或更新过的文件。
这个程序的另一个特点是当在一个特点文件上发生安全性错误或其他类型错误(当然不包括硬件错误),它不会停止拷贝工作,它将会记录下错误信息并继续完成工作。过后你可以去查看日志文件,它会告诉你发生了什么错误。大部分错误都是由于安全配置问题产生的。
背景知识
为什么要使用多线程呢?一个原因可能一个窗口在忙碌时,你想能点击窗口上的一个按钮。另一个原因是多核时代已经来临,硬件和操作系统级别的多任务也存在,线程无可避免,尤其当我们关注性能时。
好的,你已经决定你想在.Net中使用多线程。你可以使用BackgroundWorker,
不过我假定你应该从System.Threading
开始,并直接使用Thread
类。个人看来,它更容易使用,并且更具灵活性。
那么线程到底是什么呢?它就好比于一个源自于你的主程序的另一个完全分离的程序。一旦线程启动,主程序对线程完全不知道。它们生死自控。要启动一个或两个线程,你可能想知道如何创建线程,如何传递数据,如何从子线程中回调主应用程序以及主应用程序如何知道是哪个子线程回调了它?下面的代码片段会回答这些问题的。
最后一点,作者将一个类实例的方法赋予了一个线程,尽管可以将主程序的方法赋予线程,但作者认为这样做更好。
'Declarefirstthreadvariable.Thiswillbeusedtocopythefiles.
PrivateCopyThreadAsThread'拷贝线程
'Delclaresecondthreadvariable.Thiswillbeusedtocountthefoldersandfiles.
PrivateCountThreadAsThread'统计线程
统计线程一般在拷贝线程前面完成,除非你拷贝的文件数目很小,那种情况下一切都发生的很快。
另一方面,统计线程结束后,总文件数目会被统计出来,从而用来设置ProgressBar1.Max
属性,
下面是启动拷贝线程和统计线程的代码:
'开始拷贝
'Validatefromandtofolders
IfNotSetRootPathThenExitSub
IfNotCheckFromPath()ThenExitSub
IfNotCheckToPath()ThenExitSub
'Createaninstanceofthecopyclassthatwillbeassignedtothefirstthread.
DimFileCopyAsNewCopyClass(Me)
'Setrequiredproperties
FileCopy.FromPath=FromPathTextbox.Text
FileCopy.ToPath=ToPathTextbox.Text&_rootDir
FileCopy.StartDateTime=DateTime.Now
'Savelogfilename
_logFile=FileCopy.LogFileName
'Createthethreadandassigntheclassinstanceandmethodtoexecute
'(CopyFilesinthiscase)whenthethreadisstarted.
CopyThread=NewThread(AddressOfFileCopy.CopyFiles)'拷贝线程
'Startthefirstthreadtocopythefiles.
CopyThread.Name="Copy"
CopyThread.IsBackground=True
CopyThread.Start()
'Createanotherinstanceofthecopyclassthatwillbeassignedtothesecondthread.
DimFileFolderCountAsNewCopyClass(Me)
'Setrequiredproperties
FileFolderCount.FromPath=FromPathTextbox.Text
FileFolderCount.ToPath=ToPathTextbox.Text
'Createthethreadandassigntheclassinstanceandmethodtoexecute
'(CopyFilesinthiscase)whenthethreadisstarted.
CountThread=NewThread(AddressOfFileFolderCount.GetCountData)'计数线程
'Startthesecondthreadtocountfoldersandfileswhilethecopyisrunningatthesametime.
CountThread.Name="Count"
CountThread.IsBackground=True
CountThread.Start()
'Resetformcontrols
StartCopy.Enabled=False
Panel1.Enabled=False
StopCopy.Enabled=True
EndSub
下面是终止两个线程的代码:
'终止线程
IfCopyThread.IsAliveThenCopyThread.Abort()
IfCountThread.IsAliveThenCountThread.Abort()
EndSub
主界面的两个delegate方法,用来响应子线程的回调,刷新主界面
'Iffinishedcopying
IfMessage="END"Then
lblStatus.Text="Status:CopyFinsihed.Copied"+_totalFiles.ToString+"filesin"+_totalFolders.ToString+"folders."
txtFile.Text="Copycompletedsuccessfully."
ProgressBar1.Value=ProgressBar1.Maximum
CopyThread.Abort()
CountThread.Abort()
ExitSub
EndIf
'Showcurrentfile
txtFile.Text="Copying:"&Message
'Updateprogressbar
IfProgressBar1.Maximum<>0ThenProgressBar1.Value=_totalFiles-(_totalFiles-CopiedFiles)
'Updatestatus(TotalFilesnotzeromeanscountinghasfinished)
If_totalFiles<>0Then
lblStatus.Text="Status:Copying.Thereare"+_totalFiles.ToString+"filesin"+_totalFolders.ToString+"folders.Filescopiedsofar"&CopiedFiles&"."
EndIf
'SaveforCountThreadMessage()
_copiedFiles=CopiedFiles
EndSub
PublicSubCountThreadMessage()SubCountThreadMessage(ByValThreadNameAsString,ByValFilesAsLong,ByValTotalFilesAsLong,ByValFoldersAsLong,ByValMessageAsString)
'Displaycurrentcount
lblStatus.Text="Status:CopyingandCounting.Sofarthereare"+Files.ToString+"filesin"+Folders.ToString+"folders."
'SavetotalswhenfinishedcountingforCopyThreadMessage()
IfMessage="END"Then
_totalFiles=TotalFiles
_totalFolders=Folders
lblStatus.Text="Status:Copying.Thereare"+_totalFiles.ToString+"filesin"+_totalFolders.ToString+"folders.Filescopiedsofar"&_copiedFiles&"."
ProgressBar1.Maximum=_totalFiles
ProgressBar1.Value=_totalFiles-(_totalFiles-_copiedFiles)
EndIf
EndSub
负责拷贝和统计的类:
ImportsSystem.IO
PublicClassCopyClassClassCopyClass
'Thiswillholdthereferencetotheclientform
Private_clientAppAsForm
'CreateadelegatemethodthatwillmaptotheCopyThreadMessagemethodoftheclientapp
PrivateDelegateSubCallClientCopy()SubCallClientCopy(ByValThreadNameAsString,ByValFilesRemainingAsLong,ByValMessageAsString)
'CreateadelegatemethodthatwillmaptotheCountThreadMessagemethodoftheclientapp
PrivateDelegateSubCallClientCount()SubCallClientCount(ByValThreadNameAsString,ByValTotalFilesAsLong,ByValTotalFoldersAsLong,ByValFilesAsLong,ByValMessageAsString)
'Createanobjectforeachdeletegate
Private_callClientCopyAsCallClientCopy
Private_callClientCountAsCallClientCount
'Propertyvariables
Private_firstTimeAsBoolean
Private_fromPathAsString
Private_toPathAsString
Private_directoriesAsLong
Private_filesAsLong
Private_copiedFilesAsLong
Private_totalFilesAsLong
Private_fileNameAsString
Private_logFileAsStreamWriter
Private_startDateTimeAsDate
Private_logFileNameAsString
'Constants
PrivateConstLOG_FILEAsString="BackupLog.txt"
PrivateConstERR_MSGAsString="Erroraccessingfile:"
PublicSubNew()SubNew(ByRefClientAppAsBackup)
'Savethereferencetotheclientapp
_clientApp=ClientApp
'Assigndelegateobjects
_callClientCopy=AddressOfClientApp.CopyThreadMessage
_callClientCount=AddressOfClientApp.CountThreadMessage
EndSub
PublicSubCopyFiles()SubCopyFiles()
'Dotheworkofthefirstthreadhere
'Givethisthreadaname
IfThread.CurrentThread.Name=NothingThenThread.CurrentThread.Name="Copy"
'CreateanewDirectoryInfoobjectforfrompath.
DimdirAsNewDirectoryInfo(FromPath)
'CalltheGetFileSystemInfosmethod.
DimFSinfoAsFileSystemInfo()=dir.GetFileSystemInfos
'Openlogfile
OpenLog()
'Copyonefileatatimeloopinguntilallfilesarecopied
ReallyCopyFiles(FSinfo)
WriteLog("Copycompletedsuccessfully.")
'Callclientonelasttimetosignalendofcopy
CallClient(Thread.CurrentThread.Name,_copiedFiles,_totalFiles,_directories,"END")
EndSub
PublicSubGetCountData()SubGetCountData()
'Dotheworkofthesecondthreadhere
'Givethisthreadaname
IfThread.CurrentThread.Name=NothingThenThread.CurrentThread.Name="Count"
'CreateanewDirectoryInfoobjectforfrompath.
DimdirAsNewDirectoryInfo(FromPath)
'CalltheGetFileSystemInfosmethod.
DimFSinfoAsFileSystemInfo()=dir.GetFileSystemInfos
'Countfolderandfiles
CountFiles(FSinfo)
'Savetotalfilescount
_totalFiles=_files
'Sendmessagetoclientform
CallClient(Thread.CurrentThread.Name,_files,_totalFiles,_directories,"END")
EndSub
PrivateSubReallyCopyFiles()SubReallyCopyFiles(ByValFSInfoAsFileSystemInfo())
'ChecktheFSInfoparameter.
IfFSInfoIsNothingThen
ThrowNewArgumentNullException("FSInfo")
EndIf
'Iteratethrougheachitem.
DimiAsFileSystemInfo
ForEachiInFSInfo
Try
'ChecktoseeifthisisaDirectoryInfoobject.
IfTypeOfiIsDirectoryInfoThen
'CasttheobjecttoaDirectoryInfoobject.
DimdInfoAsDirectoryInfo=CType(i,DirectoryInfo)
'Iterate(recurse)throughallsub-directories.
ReallyCopyFiles(dInfo.GetFileSystemInfos())
'ChecktoseeifthisisaFileInfoobject.
ElseIfTypeOfiIsFileInfoThen
'savethefullpathandfilename
_fileName=i.FullName
'Getthecopypathnameonly
DimcopypathAsString=ToPath&Mid(_fileName,Len(FromPath)+1,Len(_fileName)-Len(FromPath)-Len(i.Name))
'Createcopypathifitdoesnotexist
IfNotDirectory.Exists(copypath)Then
Directory.CreateDirectory(copypath)
EndIf
'Getthetopathandfilename
DimtofileAsString=ToPath&Mid(_fileName,Len(FromPath)+1)
'Updatestatusinfoonclient
DimfiAsNewFileInfo(_fileName)
DimMessageAsString=_fileName&"is"&Decimal.Round(CDec(fi.Length/1048576),2)&"MBinlength."
CallClient(Thread.CurrentThread.Name,_copiedFiles,_totalFiles,_directories,Message)
'iffileexistscheckiffilehasbeenupdatedsincelastcopy
DimOkayToCopyAsBoolean=True
IfFile.Exists(tofile)Then
IfFile.GetLastWriteTime(_fileName)=File.GetLastWriteTime(tofile)Then
OkayToCopy=False
EndIf
EndIf
'Copyfilewithoverwrite
IfOkayToCopyThenFile.Copy(_fileName,tofile,True)
'Incrementcopiedfilecount
_copiedFiles+=1
EndIf
CatchexAsException
'Reporterrorbutcontinueprocessing
WriteLog(ERR_MSG&_fileName&vbCrLf&ex.Message.ToString)
EndTry
Nexti
EndSub
PrivateSubCountFiles()SubCountFiles(ByValFSInfoAsFileSystemInfo())
StaticShowCountAsLong=0
'ChecktheFSInfoparameter.
IfFSInfoIsNothingThen
ThrowNewArgumentNullException("FSInfo")
EndIf
'Iteratethrougheachitem.
DimiAsFileSystemInfo
ForEachiInFSInfo
Try
'ChecktoseeifthisisaDirectoryInfoobject.
IfTypeOfiIsDirectoryInfoThen
'Addonetothedirectorycount.
_directories+=1
'CasttheobjecttoaDirectoryInfoobject.
DimdInfoAsDirectoryInfo=CType(i,DirectoryInfo)
'Iterate(recurse)throughallsub-directories.
CountFiles(dInfo.GetFileSystemInfos())
'ChecktoseeifthisisaFileInfoobject.
ElseIfTypeOfiIsFileInfoThen
'Addonetothefilecount.
_files+=1
'displaycountforfirstfileineveryfolderthenevery200-forfasterperformance
SelectCaseShowCount
Case0
'Displaycount
CallClient(Thread.CurrentThread.Name,_files,_totalFiles,_directories,"")
CaseIs>=200
'Displaycount
CallClient(Thread.CurrentThread.Name,_files,_totalFiles,_directories,"")
'resetsodisplayisevery200filesinfolder
ShowCount=0
EndSelect
'Incrementshowcount
ShowCount+=1
EndIf
CatchexAsException
'Recorderrorthencontinue(likearesumenext)
WriteLog(ERR_MSG&_fileName&vbCrLf&ex.Message.ToString)
EndTry
Nexti
EndSub
PrivateSubCallClient()SubCallClient(ByValThreadNameAsString,ByValFilesAsLong,ByValTotalFilesAsLong,ByValDirectoriesAsLong,ByValMessageAsString)
SelectCaseThreadName
Case"Copy"
'Callthedelegatedmethod
_clientApp.Invoke(_callClientCopy,ThreadName,Files,Message)
Case"Count"
'Callthedelegatedmethod
_clientApp.Invoke(_callClientCount,ThreadName,Files,TotalFiles,Directories,Message)
EndSelect
'Letthethreadsleepbeforecontinuingsotheclientappwillhavetimetobeprocess(1millisecondisenough)
Thread.Sleep(0)
EndSub
PrivateSubOpenLog()SubOpenLog()
'Createlogfile
IfNotFile.Exists(StartDateTime&"-"&LOG_FILE)Then
Using_logFileAsStreamWriter=File.CreateText(LogFileName)
_logFile.WriteLine("Logfilenameis:"&LogFileName)
_logFile.WriteLine("BACKUPLOGFILESTARTEDAT:"&StartDateTime.ToString)
_logFile.WriteLine("================================================")
_logFile.Write("CopyingFROM:"&_fromPath)
_logFile.WriteLine()
_logFile.Write("CopyingTO:"&_toPath)
_logFile.WriteLine()
_logFile.Close()
EndUsing
EndIf
EndSub
PrivateSubWriteLog()SubWriteLog(ByValMessageAsString)
'CreateaninstanceofStreamWritertowritetexttoafile.
Using_logFileAsStreamWriter=File.AppendText(LogFileName)
'Addsometexttothefile.
_logFile.WriteLine()
_logFile.WriteLine("TIMEOFLOGENTRY:"&DateTime.Now)
'Arbitraryobjectscanalsobewrittentothefile.
_logFile.WriteLine(Message)
_logFile.Flush()
_logFile.Close()
EndUsing
EndSub
PrivateSubCloseLog()SubCloseLog()
IfFile.Exists(LogFileName)Then_logFile.Close()
EndSub
PrivatePropertyFirstTime()PropertyFirstTime()AsBoolean
Get
Return_firstTime
EndGet
Set(ByValvalueAsBoolean)
_firstTime=value
EndSet
EndProperty
PublicPropertyFromPath()PropertyFromPath()AsString
Get
Return_fromPath
EndGet
Set(ByValvalueAsString)
_fromPath=value
EndSet
EndProperty
PublicPropertyToPath()PropertyToPath()AsString
Get
Return_toPath
EndGet
Set(ByValvalueAsString)
_toPath=value
EndSet
EndProperty
PublicPropertyStartDateTime()PropertyStartDateTime()AsDate
Get
Return_startDateTime
EndGet
Set(ByValvalueAsDate)
_startDateTime=value
EndSet
EndProperty
PublicReadOnlyPropertyLogFileName()PropertyLogFileName()AsString
Get
ReturnFormat(StartDateTime,"yyMMdd-hhmmss")&"-"&LOG_FILE
EndGet
EndProperty
PublicReadOnlyPropertyDirectories()PropertyDirectories()AsLong
Get
Return_directories
EndGet
EndProperty
PublicReadOnlyPropertyFiles()PropertyFiles()AsLong
Get
Return_files
EndGet
EndProperty
PublicReadOnlyPropertyTotalFiles()PropertyTotalFiles()AsLong
Get
Return_totalFiles
EndGet
EndProperty
EndClass
作者的程序中还实现了一个比较有意思的特点,用户可以通过指定命令行参数进入后台命令行模式,而非界面模式,
Last Words
感谢作者的这篇文章,让我能参照他的思路用VC++实现了一个类似的多线程的文件备份工具。
Reference: