vb.net 委托和调用 - 多线程
vb.net delegate and invoke - Multithread
我正在尝试了解委托和调用的工作原理。
所以我构建了一个带有标签和按钮的表单。
当有人点击按钮时,文本变为 "Stop" 并且计数器开始计数。此计数器应显示在标签上。这是我的代码:
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
它的工作。
我做错了什么?
Control.Invoke()
用于在创建控件的同一线程上执行方法。由于代码在每个线程中一次只能执行一行,因此在控件的线程上执行一个方法将导致该方法为 "queued",直到该线程中该方法之前的其他代码完成为止。这使得它 thread-safe 因为不会有并发问题。
Delegate
is simply a class holding the pointer to a method. It exists so that you can use methods as if they were ordinary objects (in this case you pass it to a function). The 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
现在,还有一个问题。您的 UI 因以下原因冻结:
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()
.
回答有点长,希望对您有所帮助!
我正在尝试了解委托和调用的工作原理。
所以我构建了一个带有标签和按钮的表单。
当有人点击按钮时,文本变为 "Stop" 并且计数器开始计数。此计数器应显示在标签上。这是我的代码:
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
它的工作。
我做错了什么?
Control.Invoke()
用于在创建控件的同一线程上执行方法。由于代码在每个线程中一次只能执行一行,因此在控件的线程上执行一个方法将导致该方法为 "queued",直到该线程中该方法之前的其他代码完成为止。这使得它 thread-safe 因为不会有并发问题。
Delegate
is simply a class holding the pointer to a method. It exists so that you can use methods as if they were ordinary objects (in this case you pass it to a function). The 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
现在,还有一个问题。您的 UI 因以下原因冻结:
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()
.
回答有点长,希望对您有所帮助!