在现代应用程序中,用户界面(UI)的响应性至关重要。当执行长时间运行的任务时,如文件加密、大数据处理等,如果UI没有及时反馈,用户可能会认为程序已经崩溃或无响应。因此,实现一个有效的进度条更新机制对于提升用户体验至关重要。本文将介绍一种在VB.NET中实现多线程进度条更新的解决方案。
本文是2011年5月4日发表的同名文章的续篇。原文章试图寻找解决方案,而本文则是经过六个月测试的最终解决方案。
示例代码稍作抽象,但通过修改特定参数即可运行。在这个例子中,假设有一个加密文件的过程,它需要三个额外的参数作为输入。如果过程中没有错误,该过程将返回0;如果有错误,则返回非零值。示例代码还提供了一个取消按钮。这段代码适用于非常大的文件,以及那些不提供中间返回值以更新进度条的过程。代码分为两个类:主线程类和线程包装器,后者用于启动新线程并返回完成值。还有一个额外的类,用于获取文件的哈希值,以演示返回数据而不是整数完成值。
以下代码示例展示了如何通过为长时间运行的过程创建新线程,同时在主线程更新进度条,来实现进度条。在这个例子中,长时间运行的过程是一个API调用,它在完成之前不会返回,并返回一个整数来指示错误或成功。
Option Strict Off
Option Explicit On
Imports Microsoft.VisualBasic
Imports System
Imports System.IO
Imports System.Threading
Public Delegate Sub CallbackToEnc(ByVal var1 As Integer)
Public Delegate Sub CallbackToHash(ByVal fileHash As String)
Public Class Protect
Public rtnBlfResult As Integer
Public Shared Sub ResultCallback(ByVal var1 As Integer)
CProtect.rtnBlfResult = var1
End Sub
Public Function Encrypter(ByVal file1 As String, ByVal file2 As String, ByVal param1 As String, ByVal param2 As Integer, ByVal param3 As String) As Short
Dim iAbortLock As Boolean = False
Dim time1 As Single = 0
Dim time2 As Single = 0
CProtect.rtnBlfResult = -2 ' Initialize return.
Dim tw1 As New TWrapEnc(file1, file2, param1, param2, param3, New CallbackToEnc(AddressOf ResultCallback))
tw1.start()
T1: time2 = Timer
If cancelBttnSequence = 3 Then
If iAbortLock = False Then
tw1.thread.Abort()
iAbortLock = True
time1 = Timer
End If
End If
If tw1.thread.ThreadState = System.Threading.ThreadState.Stopped Then
GoTo T2
End If
If time1 > 0 AndAlso (Timer - time1) > 5 Then
GoTo T2a
End If
If (Timer - time1) < 0 Then
time1 = Timer
End If
If (Timer - time2) < 0 Then
time2 = Timer
End If
While (Timer - time2) < 1
SyncLock tw1.rtnLockEnc
If CProtect.rtnBlfResult = -1 Then
GoTo T2
End If
End SyncLock
End While
UpdatePgrBar()
GoTo T1
T2: tw1.join()
T2a: tw1 = Nothing
rtnBlfResult = CProtect.rtnBlfResult
If rtnBlfResult = 0 Then
Encrypter = -1 ' Successful, file2 contains encrypted form of file1.
ElseIf iAbortLock = True Then
Encrypter = -2 ' Cancel.
Else
Encrypter = 0 ' Error.
End If
End Function
Public Shared Sub UpdatePgrBar()
' Some code to update progress bar.
End Sub
End Class
Public Class TWrapEnc
Private fileX1 As String
Private fileX2 As String
Private paramX1 As String
Private paramX2 As Integer
Private paramX3 As String
Private blfRtnParam As Integer = -1
Private callBack As CallbackToEnc
Public rtnLockEnc As New Object
Public thread As System.Threading.Thread
Public Sub New(ByVal file1 As String, ByVal file2 As String, ByVal param1 As String, ByVal param2 As Integer, ByVal param3 As String, ByVal callBackDelegate As CallbackToEnc)
fileX1 = file1
fileX2 = file2
paramX1 = param1
paramX2 = param2
paramX3 = param3
callBack = callBackDelegate
thread = New System.Threading.Thread(AddressOf body)
End Sub
Public Sub start()
Me.thread.Start()
End Sub
Public Sub join()
Me.thread.Join()
End Sub
Public Sub body()
On Error GoTo E1
Me.blfRtnParam = Blowfish.FileEncrypt(Me.fileX1, Me.fileX2, Me.paramX1, Me.paramX2, Me.paramX3)
E1:
SyncLock rtnLockEnc
If Not callBack Is Nothing Then
callBack(Me.blfRtnParam)
End If
End SyncLock
Me.fileX1 = ""
Me.fileX2 = ""
Me.paramX1 = ""
End Sub
End Class
Public Class ThreadWrapperHash
Private fileNameParam As String
Private callback As CallbackToHash
Private fileHashRtnParam As String
Public rtnLockHash As New Object
Public thread As System.Threading.Thread
Public Sub New(ByVal fileName As String, ByVal callbackDelegate As CallbackToHash)
fileNameParam = fileName
callback = callbackDelegate
thread = New System.Threading.Thread(AddressOf Body)
End Sub
Public Sub start()
Me.thread.Start()
End Sub
Public Sub join()
Me.thread.Join()
End Sub
Public Sub Body()
On Error GoTo CallB
Me.fileHashRtnParam = Sha256.FileHexHash(Me.fileNameParam)
CallB:
SyncLock rtnLockHash
If Not callback Is Nothing Then
callback(Me.fileHashRtnParam)
End If
End SyncLock
Me.fileNameParam = ""
End Sub
End Class