You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

VB.NET触发自动登出时如何停止所有运行中的函数?

解决VB.NET超时登出无法停止后台函数的最佳方案

核心问题分析

  1. 全局布尔变量isStopping存在线程同步问题,后台线程无法及时感知UI线程的状态更新
  2. 未将CancellationToken传递到所有异步操作(如Google API请求),导致正在执行的请求无法被取消
  3. 登出时直接关闭窗体,后台函数仍在访问已销毁的控件(如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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 23:52:35