访问从不同线程访问的控件时如何处理无效的跨线程操作?
Posted
技术标签:
【中文标题】访问从不同线程访问的控件时如何处理无效的跨线程操作?【英文标题】: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
【问题讨论】:
【参考方案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,你的解释很好,但我现在还不太明白,但我会稍后再试,因为我想我需要实现我的实时预览。我确定它有效以上是关于访问从不同线程访问的控件时如何处理无效的跨线程操作?的主要内容,如果未能解决你的问题,请参考以下文章
跨线程操作无效:控件'listBox1'从一个>线程访问,而不是它在[重复]上创建的线程