【问题标题】:How to handle a Cross-thread operation not valid when accessing Controls accessed from a different thread?访问从不同线程访问的控件时如何处理无效的跨线程操作?
【发布时间】:2021-06-28 16:14:15
【问题描述】:

我想定期检查局域网数据库连接。
简单来说,我设置了一个运行数据库检查的计时器(它每 5 秒计时一次)。
这个过程冻结了表单,所以我尝试在线程中运行这段代码,并利用 async/await 模式来解决这个问题。
使用我当前的代码,当我尝试访问控件时,出现异常:

跨线程操作无效:控制StatusStrip1 访问自 与创建它的线程不同的线程。

我发现使用Control.Invoke() 解决了这样的问题,但我不知道如何实现它。 我正在尝试访问 ToolStrip 上的 ProgressBar 和 StatusLabel。

如何解决表单冻结问题并避免异常?

我正在使用 VB.net 2019

这是我的代码:

Private Sub MyBGThread()
    If CheckConDB(ConStringDB1) Then
        TSSPBar.BackColor = Color.Green
        TSSPBar.ForeColor = Color.Green
    Else
        TSSPBar.BackColor = Color.Red
        TSSPBar.ForeColor = Color.Red
    End If
End Sub
Private Async Sub TmrDB_Tick(sender As Object, e As EventArgs) Handles TmrDB.Tick
    ' Dim thread As New Thread(AddressOf MyBGThread)
    ' thread.Start()
    Await Task.Run(Sub() MyBGThread())
End Sub

【问题讨论】:

    标签: .net vb.net multithreading winforms


    【解决方案1】:

    试试这个

    Private Sub MyBGThread()
        If CheckConDB(ConStringDB1) Then
            SetTSSPBarColor(Color.Green, Color.Red)
        Else
            SetTSSPBarColor(Color.Red, Color.Green)
        End If
    End Sub
    
    Private Sub SetTSSPBarColor(ForeC As Color, BackC As Color)
        If Me.InvokeRequired Then
            Me.Invoke(Sub()
                          SetTSSPBarColor(ForeC, BackC)
                      End Sub)
        Else
            TSSPBar.ForeColor = ForeC
            TSSPBar.BackColor = BackC
        End If
    End Sub
    

    【讨论】:

      【解决方案2】:

      你可以这样做:

      Private Async Sub TmrDB_Tick(sender As Object, e As EventArgs) Handles TmrDB.Tick
          Dim result As Boolean = Await Task.Run(Function() CheckConDB(ConStringDB1))
          TSSPBar.BackColor = If(result, Color.Green, Color.Red)
          TSSPBar.ForeColor = If(result, Color.Green, Color.Red)
      End Sub
      

      【讨论】:

      • 干得好 Idle_Mind,非常接近我的代码,它可以工作......谢谢
      【解决方案3】:

      如果您的同步 CheckConDB() 方法可以转换为异步方法,那么您可以更轻松地等待它的结果。如果它不能是异步的,有几个选项:

      (我的建议是不要在 Timer.Tick 事件中等待,因为您不知道等待的方法需要多少时间才能完成。无论如何,计时器都会滴答作响)。

      使用BeginInvoke()
      此方法发布到 UI 线程并且不会阻塞。你不需要检查InvokeRequired,这个方法可以安全地从同一个线程或工作线程中调用。

      添加一个存储CancellationTokenSource的字段:

      Private checkDbCts As CancellationTokenSource = Nothing
      

      Form.Load 中(或者当您决定这样做时,但在您需要与之交互的控件创建了它们的句柄之后)运行此任务,指定调用CheckConDB() 之间的间隔;传递一个由您的 CancellationTokenSource 生成的CancellationToken

      checkDbCts = New CancellationTokenSource()
      Task.Run(Function() MyBGThread(1000, bgThreadCts.Token))
      

      任务将在每次调用CheckConDB() 之前等待[Interval] 毫秒。如果Task被取消(当CancellationTokenSource.Cancel()被调用时),它将终止并退出。

      CheckConDB() 返回一个结果时,BeginInvoke() 调用ProgressUpdate 方法,根据结果的值传递一个颜色。

      Private Async Function MyBGThread(interval As Integer, token As CancellationToken) As Task
          token.ThrowIfCancellationRequested()
          Try
              While True
                  Await Task.Delay(interval, token)
                  Dim result As Boolean = CheckConDB(ConStringDB1)
                  Dim ctrlColor As Color = If(result, Color.Green, Color.Red)
                  BeginInvoke(New Action(Sub() ProgressUpdate(ctrlColor)))
              End While
          Catch tce As TaskCanceledException
              Return
          End Try
      End Function
      

      使用IProgress(Of T)委托(这是首选方法):

      添加一个存放delegate的Field,保留CancellationTokeSource之前的Field声明:

      Private checkDbProgress As IProgress(Of Color) = Nothing
      

      Form.Load() 中,创建一个新的Progress(Of T) 委托并启动任务,传递委托,设置为ProgressUpdate() 方法、一个Interval 和一个CancellationToken:

      checkDbProgress = New Progress(Of Color)(Sub(c) ProgressUpdate(c))
      checkDbCts = New CancellationTokenSource()
      Task.Run(Function() MyBGThread(checkDbProgress, 1000, checkDbCts.Token))
      

      MyBGThread() 被修改为接受 Progress(Of T) 对象。
      它的Report() 方法将调用UI 线程中的ProgressUpdate() 方法,因为IProgress(Of T) 捕获了它被初始化的线程的SynchronizationContext,并使用指定的方法委托将消息异步分派到该线程。

      Private Async Function MyBGThread(progress As IProgress(Of Color), interval As Integer, token As CancellationToken) As Task
          Try
              While True
                  Await Task.Delay(interval, token)
                  Dim result As Boolean = CheckConDB(ConStringDB1)
                  Dim ctrlColor As Color = If(result, Color.Green, Color.Red)
                  progress.Report(ctrlColor)
              End While
          Catch tce As TaskCanceledException
              Return
          End Try
      End Function
      

      ProgressUpdate() 方法在这两种情况下都会被调用,并且在这两种情况下它都在 UI 线程中执行:您可以在此处与 UI 元素进行交互。

      Private Sub ProgressUpdate(ctrlColor As Color)
          TSSPBar.BackColor = ctrlColor
          TSSPBar.ForeColor = ctrlColor
      End Sub
      

      【讨论】:

      • 谢谢 Jimi,你的解释很好,但我现在还不太明白,但我会稍后再试,因为我想我需要实现我的实时预览。我确定它有效
      猜你喜欢
      • 2017-11-02
      • 2011-01-15
      • 2016-05-21
      • 2016-06-11
      • 2015-05-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多