none
关于在类中线程如何更新窗体的问题 RRS feed

  • 问题

  • Public Class Form1
        Public b As New a
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            b.a.Start()
        End Sub
    End Class

    Public Class a
        Public a As New Threading.Thread(AddressOf Me.Hello)
        Private Delegate Sub task()

        Private Sub Updatelbl()
            Form1.Label1.Text = Rnd()
            Console.WriteLine(Form1.Label1.Text)
        End Sub

        Public Sub Hello()
            While True
                Dim worker As New task(AddressOf Updatelbl)
                worker.Invoke()
                Threading.Thread.Sleep(400)
            End While
        End Sub

    End Class

    上述代码已经利用了线程安全的INVOKE调用,但是还是无法更新窗体的控件。请高手指点下上面代码错在哪里。谢谢!
    SCLIREN
    2009年8月8日 2:01

答案

  •  您好,上面的方法虽然能解决问题,但您提到的基本类(a)却与实现的窗体耦合在一起,不利于扩展。随着代码的深入会越来越复杂。
    我重新设计一下,请参考:
    1、创建一个新的程序集,名为ClassLibrary1,把基本类移到这个程序集下。
         这个程序集有两个声明一个类一个委托,代码如下:
        Public Class a
        Public Sub New()
        End Sub
        Public Sub New(ByVal syncInvoke As System.ComponentModel.ISynchronizeInvoke, ByVal updateTask As UpdateTask)
            _SyncInvoke = syncInvoke
            _UpdateTask = updateTask
        End Sub
        Private _SyncInvoke As System.ComponentModel.ISynchronizeInvoke
        Public _UpdateTask As UpdateTask

        Public a As New Threading.Thread(AddressOf Me.Hello)
        Public Sub Hello()
            While True
                Try
                    _SyncInvoke.Invoke(_UpdateTask, Nothing)
                    Threading.Thread.Sleep(400)
                Catch ex As Exception
                    Return

                End Try

            End While
        End Sub
    End Class

    Public Delegate Sub UpdateTask()    


    2、窗体中实现委托并传递给基本类,代码如下
    Imports ClassLibrary1
    Public Class Form2
        Dim _update As New UpdateTask(AddressOf Updatelbl)
        Private b As New ClassLibrary1.a(Me, _update)
        Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            b.a.Start()
        End Sub
        Private Sub Updatelbl()
            Me.Label1.Text = Rnd()

        End Sub
    End Class

    • 已标记为答案 SCLIREN 2009年8月15日 23:46
    2009年8月12日 8:15

全部回复

  • Public Class Form1
        Public a As New Threading.Thread(AddressOf Me.Hello)
        Private Delegate Sub task()

        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            a.Start()
        End Sub

        Private Sub Updatelbl()
            Label1.Text = Rnd()
            Console.WriteLine(Label1.Text)
        End Sub

        Public Sub Hello()
            While True
                Dim worker As New task(AddressOf Updatelbl)
                Me.Invoke(worker)
                Threading.Thread.Sleep(400)
            End While
        End Sub
    End Class

    试试这样子

    2009年8月8日 2:30
  • 你好 你在窗体的构造函数中 添加Control.CheckForIllegalCrossThreadCalls = false; 试试呢
    Wenn ich dich hab’,gibt es nichts, was unerträglich ist.坚持不懈!http://hi.baidu.com/1987raymond
    2009年8月9日 4:58
    版主
  • 您好,应调用窗体的Invoke, Invoke会触发一个委托,在委托中修改窗体的控件值。
    代码如一楼所示。
    这里的原理是Control的Invoke方法会把线程重新邦回窗体主线程。从而确保对窗体的修改在主线程中进行。
    这个主题我曾和另一位朋友讨论过。(c#版本的)
    请参考:http://social.microsoft.com/Forums/zh-CN/2212/thread/b55060e1-6ae7-4f54-91a8-459faf64ab2a
    2009年8月9日 13:50
  • 各位回复的兄弟请注意,我的本意是构造一个基本类,然后这个基本类有自己的任务。但是他其中的一个任务是更新界面。
    由于这个基本类内部运作时靠线程来执行,因此我需要在类中跨线程调用窗体控件。
    由此产生了上面的问题。
    很多人在这里要求我INVOKE一定要在窗体里面调用,这个我试过是可以的,但是这个答案却偏离了问题的本意。希望大家能够根据上面的说明帮忙想想。谢谢。
    SCLIREN
    2009年8月10日 13:33
  • 你好 你在窗体的构造函数中 添加Control.CheckForIllegalCrossThreadCalls = false; 试试呢
    Wenn ich dich hab’,gibt es nichts, was unerträglich ist.坚持不懈! http://hi.baidu.com/1987raymond

    我给出一段可行但是不稳定的代码给大家参考一下:
    Public Class Form1
        Public b As New a
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            Control.CheckForIllegalCrossThreadCalls = False
            b.a.Start(Me)
        End Sub
    End Class

    Public Class a
        Public a As New Threading.Thread(AddressOf Me.Hello)
        Private Delegate Sub task(ByVal objs As Object)

        Private Sub Updatelbl(ByVal objs As Object)
            Dim frm As Form1 = CType(objs, Form1)
            frm.Label1.Text = Rnd()
            Console.WriteLine(frm.Label1.Text)
        End Sub

        Public Sub Hello(ByVal objs As Object)
            While True
                Dim worker As New task(AddressOf Updatelbl)
                worker.Invoke(objs)
                Threading.Thread.Sleep(400)
            End While
        End Sub

    End Class

    在CSDN经过某位高手提示后做出了上述代码。LABEL1确实可以更新了。但是界面却不是很稳定。经常在托盘以后出现界面失控的问题。
    SCLIREN
    2009年8月10日 13:35
  • 各位回复的兄弟请注意,我的本意是构造一个基本类,然后这个基本类有自己的任务。但是他其中的一个任务是更新界面。
    由于这个基本类内部运作时靠线程来执行,因此我需要在类中跨线程调用窗体控件。
    由此产生了上面的问题。
    很多人在这里要求我INVOKE一定要在窗体里面调用,这个我试过是可以的,但是这个答案却偏离了问题的本意。希望大家能够根据上面的说明帮忙想想。谢谢。
    SCLIREN

    在winform中使用多线程时,当任务中需要修改窗体控件时,必须在创建窗体的线程上进行。而Control.Invoke就是让更新工作回到窗体线程的。
    就您给出的代码扩展一下:
    Public Sub Hello()
            While True
                Dim worker As New task(AddressOf Updatelbl)
                ......//在这里的代码都是在新线程上工作
                Me.Invoke(worker)  //只有这句回到创建窗体的线程,这是winform的机制,要更新窗体控件必须回到创建窗体的线程
                .....//又在新线程上工作
                Threading.Thread.Sleep(400)
            End While
        End Sub

    请问哪点是违背了您问题的本意?
    2009年8月10日 13:59
  • 各位回复的兄弟请注意,我的本意是构造一个基本类,然后这个基本类有自己的任务。但是他其中的一个任务是更新界面。
    由于这个基本类内部运作时靠线程来执行,因此我需要在类中跨线程调用窗体控件。
    由此产生了上面的问题。
    很多人在这里要求我INVOKE一定要在窗体里面调用,这个我试过是可以的,但是这个答案却偏离了问题的本意。希望大家能够根据上面的说明帮忙想想。谢谢。
    SCLIREN

    在winform中使用多线程时,当任务中需要修改窗体控件时,必须在创建窗体的线程上进行。而Control.Invoke就是让更新工作回到窗体线程的。
    就您给出的代码扩展一下:
    Public Sub Hello()
            While True
                Dim worker As New task(AddressOf Updatelbl)
                ......//在这里的代码都是在新线程上工作
                Me.Invoke(worker)  //只有这句回到创建窗体的线程,这是winform的机制,要更新窗体控件必须回到创建窗体的线程
                .....//又在新线程上工作
                Threading.Thread.Sleep(400)
            End While
        End Sub

    请问哪点是违背了您问题的本意?

    首先,很感谢您的帮忙。但是,我早就在VB 2008试过了。 基本类不属于CONTROL类因此不可能有INVOKE事件,除非先要继承CONTROL类。所以在基本类中使用ME.INVOKE本身就是错误的调用。另外即使继承了CONTROL类。这样调用还是会有“在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke”的报错。我不知道是否因为VB 2008跟VB 2005有很大差异所致。你说的方法其实我之前就试过但是是行不通的。

    SCLIREN
    • 已编辑 SCLIREN 2009年8月11日 12:22
    2009年8月11日 12:10
  • 楼主,您好,我们在这谈了半天,无非是想说清楚,winform在多线程中修改窗体控件的原理。
    我觉得的不是要否定我们先前讨论的,那是原理。而是要想办法怎么把窗体类传递,不一定要继承。
    Public Class Form1
        Public b As New a(Me)

        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            Control.CheckForIllegalCrossThreadCalls = False
            b.a.Start()

        End Sub
    End Class

    Public Class a
        Public Sub New()
        End Sub
        Public Sub New(ByVal form As Form1)
            _Form = form
        End Sub
        Private _Form As Form1
        Public a As New Threading.Thread(AddressOf Me.Hello)
        Private Delegate Sub UpdateTask()

        Private Sub Updatelbl()
            _Form.Label1.Text = Rnd()

        End Sub

        Public Sub Hello()
            While True
                Dim update As New UpdateTask(AddressOf Updatelbl)
                _Form.Invoke(update)
                Threading.Thread.Sleep(400)
            End While
        End Sub
    End Class

    • 已编辑 Jiyuan 2009年8月11日 16:12
    2009年8月11日 16:11
  • 使用上述方法,如果出现“在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke”错误。
    是因为那个无限循环造成的。当窗体消失时,线程还执行,所以报错。可以捕获异常来处理。
    例如:
    Public Sub Hello()
            While True
                Try

                    Dim update As New UpdateTask(AddressOf Updatelbl)
                    _Form.Invoke(update)
                    Threading.Thread.Sleep(400)
                Catch ex As Exception
                    Return

                End Try

            End While
        End Sub

    2009年8月11日 16:27
  •  您好,上面的方法虽然能解决问题,但您提到的基本类(a)却与实现的窗体耦合在一起,不利于扩展。随着代码的深入会越来越复杂。
    我重新设计一下,请参考:
    1、创建一个新的程序集,名为ClassLibrary1,把基本类移到这个程序集下。
         这个程序集有两个声明一个类一个委托,代码如下:
        Public Class a
        Public Sub New()
        End Sub
        Public Sub New(ByVal syncInvoke As System.ComponentModel.ISynchronizeInvoke, ByVal updateTask As UpdateTask)
            _SyncInvoke = syncInvoke
            _UpdateTask = updateTask
        End Sub
        Private _SyncInvoke As System.ComponentModel.ISynchronizeInvoke
        Public _UpdateTask As UpdateTask

        Public a As New Threading.Thread(AddressOf Me.Hello)
        Public Sub Hello()
            While True
                Try
                    _SyncInvoke.Invoke(_UpdateTask, Nothing)
                    Threading.Thread.Sleep(400)
                Catch ex As Exception
                    Return

                End Try

            End While
        End Sub
    End Class

    Public Delegate Sub UpdateTask()    


    2、窗体中实现委托并传递给基本类,代码如下
    Imports ClassLibrary1
    Public Class Form2
        Dim _update As New UpdateTask(AddressOf Updatelbl)
        Private b As New ClassLibrary1.a(Me, _update)
        Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            b.a.Start()
        End Sub
        Private Sub Updatelbl()
            Me.Label1.Text = Rnd()

        End Sub
    End Class

    • 已标记为答案 SCLIREN 2009年8月15日 23:46
    2009年8月12日 8:15
  • 这样修改后,基本类中不依赖于任何具体实现。达到解耦的目的。 不知道是不是符合您的想法?
    2009年8月12日 8:22
  • 非常感谢,这应该就是我想要的了。Invoke方面的原理学习的不太好因此没想到这种方法。
    SCLIREN
    2009年8月15日 23:47
  • 不客气,互相交流。:)
    2009年8月16日 4:45