VB.NET WinForm中死循環執行耗時操作,導致UI界面凍結,Button2需點擊兩次才能響應停止命令。
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Do While True
Application.DoEvents() ' 關鍵:處理待處理Windows消息
If Stop_Enable Then Exit Do
' 業務邏輯...
ListBox1.Items.Add(n)
ListBox1.TopIndex = ListBox1.Items.Count - 1
n += 1
Thread.Sleep(100) ' 降低CPU佔用
Loop
End Sub
Imports System.Threading.Tasks
Private cts As CancellationTokenSource
Private uiTask As Task
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Button1.Enabled = False
Button2.Enabled = True
cts = New CancellationTokenSource()
uiTask = Task.Run(Sub() RunLoop(cts.Token), cts.Token)
Try
Await uiTask
Catch ex As OperationCanceledException
' 正常取消
Finally
Button1.Enabled = True
Button2.Enabled = False
End Try
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
cts?.Cancel() ' 發送取消信號
End Sub
Private Sub RunLoop(token As CancellationToken)
Dim n = 0
While Not token.IsCancellationRequested
' 後台線程計算
Dim item = n.ToString()
n += 1
' 跨線程更新UI
Me.Invoke(Sub()
ListBox1.Items.Add(item)
ListBox1.TopIndex = ListBox1.Items.Count - 1
End Sub)
Thread.Sleep(100)
End While
End Sub
Private worker As New BackgroundWorker()
Private Sub Form1_Load(...) Handles MyBase.Load
worker.WorkerSupportsCancellation = True
worker.WorkerReportsProgress = True
AddHandler worker.DoWork, AddressOf Worker_DoWork
AddHandler worker.ProgressChanged, AddressOf Worker_ProgressChanged
End Sub
Private Sub Worker_DoWork(sender, e)
Dim bw = CType(sender, BackgroundWorker)
Dim n = 0
While Not bw.CancellationPending
bw.ReportProgress(0, n) ' 傳遞數據
n += 1
Thread.Sleep(100)
End While
End Sub
Private Sub Worker_ProgressChanged(sender, e)
ListBox1.Items.Add(e.UserState)
ListBox1.TopIndex = ListBox1.Items.Count - 1
End Sub
| 方案 | 優點 | 缺點 | 推薦指數 |
|---|---|---|---|
| DoEvents | 改動最小、兼容舊代碼 | 重入風險、性能差、不利於複雜邏輯 | ★★☆ |
| Task/Async-Await | 現代標準、異常處理好、可組合 | 需.NET 4.5+、學習成本 | ★★★★★ |
| BackgroundWorker | 事件模型直觀、VS設計器支持 | 舊API、功能受限 | ★★★ |