vb.net委托和调用 - 多线程

时间:2017-01-28 07:52:46

标签: vb.net multithreading delegates thread-safety invoke

我正在尝试了解委托和调用工作的方式。

所以我构建了一个带有标签和按钮的表单。

当有人点击按钮时,文字会变为"停止"并且计数器开始计数。此计数器应显示在标签上。这是我的代码:

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它的工作。

我做错了什么?

1 个答案:

答案 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()

答案有点长,但我希望它有所帮助!