VB.NET触发自动登出时如何停止所有运行中的函数?
解决VB.NET超时登出无法停止后台函数的最佳方案
核心问题分析
- 全局布尔变量
isStopping存在线程同步问题,后台线程无法及时感知UI线程的状态更新 - 未将
CancellationToken传递到所有异步操作(如Google API请求),导致正在执行的请求无法被取消 - 登出时直接关闭窗体,后台函数仍在访问已销毁的控件(如DataGridView、ProgressForm)引发异常
方案一:使用CancellationToken(推荐)
这是.NET异步编程中取消任务的标准方式,能可靠终止所有异步操作并清理资源。
1. 定义类级别的CancellationTokenSource
Private cts As CancellationTokenSource
2. 修改UpdateTagsAsync函数,集成取消逻辑
Private Async Function UpdateTagsAsync(ct As CancellationToken) As Task(Of Boolean) Dim userEmail As String = Await GoogleApiHelper.GetCurrentUserEmailAsync().WaitAsync(ct) Dim logResultMessage As String = String.Empty Dim successTags As New List(Of String)() Dim failedTags As New List(Of String)() Dim hasTagsToUpdate As Boolean = DataGridView2.Rows.Cast(Of DataGridViewRow)().Any(Function(row) Not row.IsNewRow) If hasTagsToUpdate Then If progressForm Is Nothing OrElse progressForm.IsDisposed Then progressForm = New ProgressForm() End If progressForm.Label1.Text = "Updating Tags..." End If Try Dim service As SheetsService = GoogleApiHelper.GetSheetsService() Dim updates As New List(Of ValueRange)() For Each row As DataGridViewRow In DataGridView2.Rows ' 检查是否触发取消 ct.ThrowIfCancellationRequested() If row.IsNewRow Then Continue For Dim tag As String = row.Cells(DataGridView2Column8.Index).Value.ToString() progressForm.Size = New Size(200, 33) progressForm.Label1.Size = New Size(194, 12) progressForm.Show() ' 传入取消令牌到Delay操作 Await Task.Delay(200, ct) progressForm.Label1.Text = $"Updating '{tag}'. Please wait..." Dim sheetName As String = GetTargetSheetForTag(tag) If String.IsNullOrEmpty(sheetName) Then failedTags.Add(tag) Continue For End If ' 传入取消令牌到Google API请求 Dim range As String = $"{sheetName}!A2:Q" Dim request As SpreadsheetsResource.ValuesResource.GetRequest = service.Spreadsheets.Values.Get(SpreadsheetId, range) Dim response As ValueRange = Await request.ExecuteAsync(ct) Dim values As IList(Of IList(Of Object)) = response.Values Dim rowIndexToUpdate As Integer = -1 If values IsNot Nothing AndAlso values.Count > 0 Then For i As Integer = 0 To values.Count - 1 ct.ThrowIfCancellationRequested() Dim rowData As IList(Of Object) = values(i) If rowData.Count > 6 AndAlso rowData(6).ToString() = tag Then rowIndexToUpdate = i + 2 Exit For End If Next End If If rowIndexToUpdate < 0 Then failedTags.Add(tag) Continue For End If Dim updateList As New List(Of ValueRange) From { New ValueRange() With {.Range = $"{sheetName}!D{rowIndexToUpdate}", .Values = New List(Of IList(Of Object)) From {New List(Of Object)() From {row.Cells(DataGridView2Column5.Index).Value}}}, New ValueRange() With {.Range = $"{sheetName}!E{rowIndexToUpdate}", .Values = New List(Of IList(Of Object)) From {New List(Of Object)() From {row.Cells(DataGridView2Column6.Index).Value}}}, New ValueRange() With {.Range = $"{sheetName}!H{rowIndexToUpdate}", .Values = New List(Of IList(Of Object)) From {New List(Of Object)() From {row.Cells(DataGridView2Column9.Index).Value}}}, New ValueRange() With {.Range = $"{sheetName}!I{rowIndexToUpdate}", .Values = New List(Of IList(Of Object)) From {New List(Of Object)() From {row.Cells(DataGridView2Column10.Index).Value}}}, New ValueRange() With {.Range = $"{sheetName}!J{rowIndexToUpdate}", .Values = New List(Of IList(Of Object)) From {New List(Of Object)() From {row.Cells(DataGridView2Column11.Index).Value}}}, New ValueRange() With {.Range = $"{sheetName}!L{rowIndexToUpdate}", .Values = New List(Of IList(Of Object)) From {New List(Of Object)() From {row.Cells(DataGridView2Column13.Index).Value}}}, New ValueRange() With {.Range = $"{sheetName}!O{rowIndexToUpdate}", .Values = New List(Of IList(Of Object)) From {New List(Of Object)() From {row.Cells(DataGridView2Column16.Index).Value}}}, New ValueRange() With {.Range = $"{sheetName}!Q{rowIndexToUpdate}", .Values = New List(Of IList(Of Object)) From {New List(Of Object)() From {row.Cells(DataGridView2Column18.Index).Value}}} } updates.AddRange(updateList) Dim batchUpdateRequest As New BatchUpdateValuesRequest() With { .ValueInputOption = "RAW", .Data = updates } Dim batchUpdate As SpreadsheetsResource.ValuesResource.BatchUpdateRequest = service.Spreadsheets.Values.BatchUpdate(batchUpdateRequest, SpreadsheetId) ' 传入取消令牌到批量更新请求 Await batchUpdate.ExecuteAsync(ct) successTags.Add(tag) updates.Clear() Next If progressForm IsNot Nothing AndAlso Not progressForm.IsDisposed AndAlso hasTagsToUpdate Then progressForm.Close() End If Dim successBulletPoints As String = If(successTags.Count > 0, "Update Successful for:" & vbCrLf & String.Join(vbCrLf, successTags.Select(Function(t) "• " & t)), "No tags were updated") Dim failureBulletPoints As String = If(failedTags.Count > 0, "Failed to update tags:" & vbCrLf & String.Join(vbCrLf, failedTags.Select(Function(t) "• " & t)), String.Empty) Dim messageBoxText As String = successBulletPoints & vbCrLf & vbCrLf & failureBulletPoints If successTags.Count > 0 Then For Each tagItem In successTags GoogleApiHelper.LogUpdateAction(userEmail, tagItem, "Successfully updated tag: " & tagItem, "Update Tags") Next End If If failedTags.Count > 0 Then For Each failedTag In failedTags GoogleApiHelper.LogUpdateAction(userEmail, failedTag, "Failed to update tag: " & failedTag, "Update Tags") Next End If MessageBox.Show(messageBoxText, "Update Status", MessageBoxButtons.OK, MessageBoxIcon.Information) Return successTags.Count > 0 Catch ex As OperationCanceledException ' 处理取消异常,清理资源 If progressForm IsNot Nothing AndAlso Not progressForm.IsDisposed Then progressForm.Close() End If MessageBox.Show("Operation canceled due to logout.", "Canceled", MessageBoxButtons.OK, MessageBoxIcon.Information) Return False Catch ex As Exception If progressForm IsNot Nothing AndAlso Not progressForm.IsDisposed Then progressForm.Close() End If logResultMessage = "Update Error - " & ex.Message Return False Finally If Not String.IsNullOrEmpty(logResultMessage) Then GoogleApiHelper.LogUpdateAction(userEmail, "Tags", logResultMessage, "Update Tags") End If End Try End Function
3. 修改登出逻辑,先取消后台任务再执行登出
Public Sub PerformLogout() If Application.OpenForms("HomePage") IsNot Nothing Then Application.OpenForms("HomePage").Invoke(Async Sub() ' 取消后台任务 If cts IsNot Nothing Then cts.Cancel() Try ' 等待任务完成清理 Catch ex As Exception ' 忽略取消异常 End Try cts.Dispose() cts = Nothing End If Dim tokenFilePaths As String() = {"token.json"} For Each path In tokenFilePaths If System.IO.File.Exists(path) Then Try System.IO.File.Delete(path) Catch ex As Exception MessageBox.Show("Failed to delete token file at " & path & ": " & ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error) End Try End If Next GoogleApiHelper.Cleanup() For Each frm As Form In Application.OpenForms.Cast(Of Form).ToArray() If Not TypeOf frm Is LoginForm Then frm.Close() End If Next Dim loginForm As New LoginForm() loginForm.Show() MessageBox.Show("You have been logged out due to inactivity.", "Logout", MessageBoxButtons.OK, MessageBoxIcon.Information) End Sub) End If End Sub
4. 启动UpdateTagsAsync时初始化令牌
' 启动更新任务的代码 cts = New CancellationTokenSource() Await UpdateTagsAsync(cts.Token)
方案二:修复全局布尔变量的线程同步问题(备选)
如果坚持使用全局变量,需用Interlocked类保证线程可见性:
' 定义线程安全的停止标记 Private isStopping As Integer = 0 ' 0=未停止,1=已停止 ' 设置停止状态 Interlocked.Exchange(isStopping, 1) ' 检查停止状态 If Interlocked.CompareExchange(isStopping, 0, 0) = 1 Then ' 执行停止逻辑 End If
注意:此方式无法取消正在执行的Google API请求,仅能终止循环和后续操作,优先级低于CancellationToken方案。
额外注意事项
- 移除
Application.DoEvents(),异步操作中Await已能保证UI响应,避免UI线程混乱 - 访问控件前检查窗体是否已销毁(如
If Not Me.IsDisposed Then),避免空引用异常 - 确保GoogleApiHelper的自定义异步方法也支持传入CancellationToken,实现全链路取消
内容的提问来源于stack exchange,提问作者Leopard
相关产品推荐
相关产品推荐

