我正在尝试了解委托和调用工作的方式。
所以我构建了一个带有标签和按钮的表单。
当有人点击按钮时,文字会变为"停止"并且计数器开始计数。此计数器应显示在标签上。这是我的代码:
Public Class Form1
Private t1 As Thread
Private sek_ As Integer = 0
Private Sub btn_read_Click(sender As Object, e As EventArgs) Handles btn_read.Click
If btn_read.Text = "Read" Then
btn_read.Text = "Stop"
t1 = New Thread(AddressOf stopw_)
t1.Start()
Else
lbl_stopw_.Text = ""
btn_read.Text = "Read"
End If
End Sub
Private Delegate Sub stopw_D()
Private Sub stopw_()
Do While btn_read.Text = "Stop"
sek_ = sek_ + 1
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke(New stopw_D(AddressOf stopw_))
Else
lbl_stopw_.Text = sek_
End If
Thread.Sleep(1000)
Loop
sek_ = 0
If t1.IsAlive Then t1.Abort()
End Sub
End Class
如果我开始调试,表单仍会冻结,标签不会更新。如果我删除所有委托并调用内容并使用Me.CheckForIllegalCrossThreadCalls = False
它的工作。
我做错了什么?
答案 0 :(得分:1)
Control.Invoke()
用于在创建控件的同一线程上执行方法。由于代码只能在每个线程中一次执行一行,因此在控件的线程上执行方法将导致该方法“排队”,直到该方法之前该线程中的其他代码完成为止。这使它具有线程安全性,因为不存在并发问题。
Delegate
只是一个包含指向方法的指针的类。它的存在使得您可以像使用普通对象一样使用方法(在这种情况下,您将它传递给函数)。 AddressOf
运算符是创建委托的快捷方式。
现在,您的代码中存在一些问题。首先,您不应尝试从后台线程访问或修改 ANY UI元素。无论何时想要修改或检查控件,都必须 始终 调用。
更具体地说,我在谈论你的While
- 循环:
'You can't check the button here without invoking.
Do While btn_read.Text = "Stop"
最好是创建一个Boolean
变量来指示线程应该运行的时间。
Private t1 As Thread
Private sek_ As Integer = 0
Private ThreadActive As Boolean = False
在启动thead之前将ThreadActive
设置为True
,然后在线程的While
- 循环检查中设置:
Do While ThreadActive
现在,还有另一个问题。您的用户界面因此冻结:
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke(New stopw_D(AddressOf stopw_))
从不 调用线程运行的 相同的 方法!这样做会重新开始处理,但是在UI线程上。你的循环使UI线程完全忙,这就是为什么它不会重绘自己。
因此,当您要更新某些内容时,请始终调用单独的方法。如果您定位 .NET 4.0或更高版本,则可以使用lambda expressions作为快速内联代理:
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke( _
Sub()
lbl_stopw_.Text = sek_
End Sub)
Else
lbl_stopw_.Text = sek_
End If
但是,如果您的目标是 .NET 3.5或更低版本,则必须坚持使用代理的常规方式:
'Outside your thread.
Private Delegate Sub UpdateLabelDelegate(ByVal Text As String)
Private Sub UpdateLabel(ByVal Text As String)
lbl_stopw_.Text = Text
End Sub
'In your thread.
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke(New UpdateLabelDelegate(AddressOf UpdateLabel), sek_)
Else
UpdateLabel(sek_)
End If
或者,为了最大限度地减少您必须编写的代码量,您可以创建一个extension method来为您调用:
Imports System.Runtime.CompilerServices
Public Module Extensions
<Extension()> _
Public Sub InvokeIfRequired(ByVal Control As Control, ByVal Method As [Delegate], ByVal ParamArray Parameters As Object())
If Parameters Is Nothing OrElse _
Parameters.Length = 0 Then Parameters = Nothing 'If Parameters is null or has a length of zero then no parameters should be passed.
If Control.InvokeRequired = True Then
Control.Invoke(Method, Parameters)
Else
Method.DynamicInvoke(Parameters)
End If
End Sub
End Module
用法, .NET 4.0或更高版本:
lbl_stopw_.InvokeIfRequired( _
Sub()
lbl_stopw_.Text = sek_
End Sub)
用法, .NET 3.5或更低版本:
lbl_stopw_.InvokeIfRequired(New UpdateLabelDelegate(AddressOf UpdateLabel), sek_)
使用此扩展方法时,无需在任何地方编写InvokeRequired
个检查:
Do While btn_read.Text = "Stop"
sek_ = sek_ + 1
lbl_stopw_.InvokeIfRequired(New UpdateLabelDelegate(AddressOf UpdateLabel), sek_)
Thread.Sleep(1000)
Loop
最后,这是不必要的:
If t1.IsAlive Then t1.Abort()
当线程到达If
- 语句时,它将始终处于活动状态,因为它尚未退出stopw_
方法。但是一旦线程退出该方法,它将正常结束,因此没有理由调用Abort()
。
答案有点长,但我希望它有所帮助!