none
VB2008 跨執行緒存取控制項的使用 RRS feed

  • 問題

  • 各位大大:
    小弟在之前提出"VB2008 SerialPort.DataReceived 事件的應用"這個問題中,Bill Chung 大大有告知小弟關於跨執行緒存取控制項的一些使用方式
    只是目前當小弟將我由RS232傳回的回應碼進行分類後,再套入模組解碼輸出卻無法顯示在form的控制項上,請問是否有地方是小弟忽略或是錯誤的地方呢?
    dim RXD as string '設為全域變數
    收碼程式
    Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
            RXD = ""
            Dim i As Integer = 0
            Try
                While Microsoft.VisualBasic.Right(RXD, 1) <> Chr(13)
                    System.Threading.Thread.Sleep(50)
                    RXD &= SerialPort1.ReadExisting
                    i += 1
                    If i > 2 Then
                        Exit While
                    End If
                End While
                If i <= 2 Then
                    ChooseThreads(1)
                Else
                End If
            Catch ex As Exception
            Finally
                Application.DoEvents()
                AddHandler SerialPort1.DataReceived, AddressOf SerialPort1_DataReceived
                bIsChanged = True
                System.Threading.Thread.Sleep(50)
                myResetEvent.Set()
            End Try
    End Sub
    Public Sub ChooseThreads(ByVal threadNumber As Integer)
                Case 1
                       FactorialThread = New System.Threading.Thread(AddressOf RXDtranfer)
                    FactorialThread.Start()
            End Select
        End Sub

        Public Sub RXDtranfer(ByVal P As String)   '判斷字元長度顯示
            Dim strLen As Integer = Len(P)
            Select Case Len(P)
                Case 15
                    Label1.Text = P
                  ALMD1(P)        
            End Select
        End Sub

    建立一個Module1.vb
    Public Function ALMD1(ByVal ALMMG As String) As String
            For i As Integer = 0 To 7
                Dim ALMG1 As String = Mid(ALMMG, 8 + (i * 4), 4)
                Select Case [i]
                    Case 0
                        ALMDISP1(Hex2Bin(ALMG1))  '將ALMG1轉成16進制轉成2進制
                End Select
            Next i
        End Function

        Public Function ALMDISP1(ByVal ALMMG As String)
            Dim ALM_R As Integer
            Static ALM_NUM As Integer
            Dim ALM1000 As Boolean 
            Dim ALM1006 As Boolean
            Static I_0 As Boolean 
            For ALM As Integer = 0 To 15
                Select Case [ALM]
                    Case 0
                        ALM_R = CInt(Mid$(ALMMG, 16 - ALM, 1))
                        If ALM_R = 1 Then
                            ALM1000 = True
                        Else
                            ALM1000 = False
                        End If
                        If ALM1000 = True Then
                            If I_0 <> ALM1000 Then
                                I_0 = ALM1000
                                Panel2.BackColor = Color.Black
                                ListBox1.Items.Add("   001     " + Now + "     " + "1111111111111")
                                ALM_NUM = ALM_NUM + 1
                            End If
                        End If
                        If ALM1000 = False Then
                            If I_0 <> ALM1000 Then
                                I_0 = ALM1000
                                fstr = "001"
                                For i = 0 To ListBox1.Items.Count - 1
                                    If ListBox1.Items(i) Like "*" & fstr & "*" Then
                                        fstr = ListBox1.Items(i) '比對筆數資料
                                        ListBox1.Items.Remove(fstr)
                                        Exit For
                                    End If
                                Next i
                                ALM_NUM = ALM_NUM - 1

                            End If
                        End If
                End Select
            Next ALM
    End Function


    以上程式若放置在form1的程式中則可以顯示出訊息來..但放在Module中則無訊息輸出,但利用程式中斷來看,動作及值均會產生,請問各位大大..是哪邊小弟疏忽了呢?


    新手上路
    2009年4月7日 上午 07:32

解答

  • eblue:
         原因出在Module裡,因為Module中的Form1,並不是正在執行畫面的那個Form1.
         你用多執行緒呼叫了Module中的方法,結果就出現了一個新的Form1
         我們把底下程式改一下
         Public Function MSG(ByVal item As Integer)
            Select Case item
                Case 1
                    Form1.AddList("test1")
                Case 2
                    Form1.AddList("test2")
                Case 3
                    Form1.AddList("test3")
            End Select
            Form1.Text="222"
            Form1.Show()
           
        End Function
         你會發現它多彈出一個視窗出來,基本上這個Form1是繼承你原來的Form1 Class新的Instance
         於是得到一個結論,如果在多執緒環境要從 Module回去Call主畫面的方法時,要將Form物件的參照傳進來  
         所以程式要改成
         Public Function MSG(ByVal item As Integer, ByRef obj As Form1)
            Select Case item
                Case 1
                    obj.AddList("test1")
                Case 2
                    obj.AddList("test2")
                Case 3
                    obj.AddList("test3")
            End Select       
        End Function
        主畫面的呼叫則要改成
         MSG(TextBox1.Text,me)
     ================================================
    說實在話,我雖然常在寫多執緒的程式,也沒這樣搞過,因為實在不習慣從其它執行緒直接控制主畫面的控制項.
    Module中最好還是專心處理值的問題,不論是變數還是物件,再把值回傳給主畫面的程式就好.
    用Module這樣去控制主畫面控制項,感覺有點不是那麼的容易除錯.
    • 已標示為解答 eblue 2009年4月15日 上午 02:09
    2009年4月13日 上午 10:39
    版主

所有回覆

  • 程式中的ListBox1應該是form1.ListBox1 (在Module中的語法)
    新手上路
    2009年4月7日 上午 07:34
  • eblue
            不要從Module去呼叫Form的控制項,Module的內容最好只處理變數.
            將變數傳回Form,再由Form做控制項處理

    2009年4月7日 上午 08:37
    版主
  • eblue
    Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
           這個事件的RemoveHandler SerialPort1.DataReceived, AddressOf SerialPort1_DataReceived哪去了?
           沒這一段可能會有問題喔.


    2009年4月7日 上午 11:53
    版主
  • 一般來說,會在 form1 上做一個方法來給外部的函數或其他 form 來呼叫,例如:

    Form1
    Public Function AddList(Byval sMessage As String)
    ...

    Module1
    Form1.AddList("某行")

    你使用多緒時,還有委派問題,詳細相關討論請搜尋 thread 委派:
    http://social.msdn.microsoft.com/Search/zh-TW/?Refinement=112&query=thread+%e5%a7%94%e6%b4%be&rq=meta:Search.MSForums.ForumID(78e6145b-ef29-4a75-a0ff-d4d3828dcd1a)&rn=Visual+Basic+%e8%ab%96%e5%a3%87


    論壇是網友平等互助 保證解答請至 微軟技術支援服務
    2009年4月7日 下午 12:21
  • 那些是我沒貼出來啦!!抱歉!!因為我想只針對回應碼的部分做運算


    新手上路
    2009年4月8日 上午 10:26
  • 感謝大大提醒~~糾正了我的觀念!感謝你們!這些部分我會再來去試看看
    新手上路
    2009年4月8日 上午 10:28
  • 各位大大:
    以下是小弟修正後的程式碼:
     Public Sub RXDtranfer(ByVal P As String)   '判斷字元長度顯示
            Dim strLen As Integer = Len(P)    '假設 Len(P)=15
          Select Case Len(P)
                Case 15
                    Label1.Text = P
                    ChooseThreads(1)
                Case 35
                    ChooseThreads(2)
               ......略
                 End Select
    End Sub
    Public Sub ChooseThreads(ByVal threadNumber As Integer)
            Select Case threadNumber
                Case 1
                    FactorialThread = New System.Threading.Thread(AddressOf work1)
                    FactorialThread.Start()
                Case 2
                    FactorialThread = New System.Threading.Thread(AddressOf work2)
                    FactorialThread.Start()
            End Select
    End Sub
    Private Sub work1()
            ALMD1(RXD)
    End Sub

    Pblic Function AddList(ByVal sMessage As String)
            ListBox1.Items.Add(sMessage)
           Msg1(sMessage)
    End Function

    Public Function DelList(ByVal sMessage As String)
            ListBox1.Items.Remove(sMessage)
    End Function

    Delegate Sub SetMsg1(ByVal str As String)
        Private Sub Msg1(ByVal str As String)
            If Me.Label19.InvokeRequired Then
                Dim d As New SetMsg1(AddressOf Msg1)
                Me.Invoke(d, New Object() {str})
            Else
                Me.Label19.Text = str
            End If
        End Sub
    Module1.vb
    Public Function ALMDISP1(ByVal ALMMG As String)
            Dim ALM_R As Integer
            Static ALM_NUM As Integer
            Dim ALM1000 As Boolean 
            Dim ALM1006 As Boolean
            Static I_0 As Boolean 
            For ALM As Integer = 0 To 15
                Select Case [ALM]
                    Case 0
                        ALM_R = CInt(Mid$(ALMMG, 16 - ALM, 1))
                        If ALM_R = 1 Then
                            ALM1000 = True
                        Else
                            ALM1000 = False
                        End If
                        If ALM1000 = True Then
                            If I_0 <> ALM1000 Then
                                I_0 = ALM1000
                                Form1.Addlist("   001     " + Now + "     " + "1111111111111")
                                ALM_NUM = ALM_NUM + 1
                            End If
                        End If
                        If ALM1000 = False Then
                            If I_0 <> ALM1000 Then
                                I_0 = ALM1000
                                fstr = "   001     " + Now + "     " + "1111111111111"
                                Form1.DelList(fstr)
                                ALM_NUM = ALM_NUM - 1                       
                            End If
                        End If
                End Select
            Next ALM
    End Function
     

    ----以上是程式輸出片段,當我利用中斷檢查 sMessage中是有值的..但還是無法輸出


    新手上路
    2009年4月13日 上午 02:35
  • eblue:
           你有把Label19 Invoke,但你沒有Invoke ListBox1,看起來又直接跨執行緒呼叫ListBox1了,你先把 ListBox1.Items.Add(sMessage)註解起來,看Label19會不會產生變化。
    2009年4月13日 上午 03:17
    版主
  • Bill大大..
    我新增那個label19的委派已將listbox1註解掉了
    我另置了一個測試程式

    Public Class Form1
        Public FactorialThread As System.Threading.Thread
        Public Sub ChooseThreads(ByVal threadNumber As Integer)
            Select Case threadNumber
                Case 1
                    FactorialThread = New System.Threading.Thread(AddressOf work1)
                    FactorialThread.Start()
                Case 2
                    FactorialThread = New System.Threading.Thread(AddressOf work2)
                    FactorialThread.Start()
            End Select
        End Sub
        Private Sub work1()
            MSG(TextBox1.Text)
        End Sub
        Private Sub work2()
            MSG1(TextBox2.Text)
        End Sub
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        End Sub
        Public Function AddList(ByVal sMessage As String)
            'ListBox1.Items.Add(sMessage)
            Msg11(sMessage)
        End Function
        Public Function DelList(ByVal sMessage As String)
            ' ListBox1.Items.Remove(sMessage)
        End Function
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            ChooseThreads(1)
        End Sub
        Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
            ChooseThreads(2)
        End Sub
        Delegate Sub SetMsg1(ByVal str As String)
        Private Sub Msg11(ByVal str As String)
            If Me.Label1.InvokeRequired Then
                Dim d As New SetMsg1(AddressOf Msg11)
                Me.Invoke(d, New Object() {str})
            Else
                Me.Label1.Text = str
            End If
        End Sub
    End Class
    Module Module1
        Public Function MSG(ByVal item As Integer)
            Select Case item
                Case 1
                    Form1.AddList("test1")
                Case 2
                    Form1.AddList("test2")
                Case 3
                    Form1.AddList("test3")
            End Select
        End Function
        Public Function MSG1(ByVal item As Integer)
            Dim mg As String
            Select Case item
                Case 1
                    mg = "test1"
                    Form1.DelList(mg)
                Case 2
                    mg = "test2"
                    Form1.DelList(mg)
                Case 3
                    mg = "test3"
                    Form1.DelList(mg)
            End Select
        End Function
    End Module
    結果仍是無法將結果輸出至label1中
    新手上路
    2009年4月13日 上午 08:50
  • 你只有用多緒,沒用委派,在 VS2005 以後強制須使用委派。

    前面有寫過:

    你使用多緒時,還有委派問題,詳細相關討論請搜尋 thread 委派:
    http://social.msdn.microsoft.com/Search/zh-TW/?Refinement=112&query=thread+%e5%a7%94%e6%b4%be&rq=meta:Search.MSForums.ForumID(78e6145b-ef29-4a75-a0ff-d4d3828dcd1a)&rn=Visual+Basic+%e8%ab%96%e5%a3%87


    論壇是網友平等互助 保證解答請至 微軟技術支援服務
    2009年4月13日 上午 09:01
  • eblue:
         原因出在Module裡,因為Module中的Form1,並不是正在執行畫面的那個Form1.
         你用多執行緒呼叫了Module中的方法,結果就出現了一個新的Form1
         我們把底下程式改一下
         Public Function MSG(ByVal item As Integer)
            Select Case item
                Case 1
                    Form1.AddList("test1")
                Case 2
                    Form1.AddList("test2")
                Case 3
                    Form1.AddList("test3")
            End Select
            Form1.Text="222"
            Form1.Show()
           
        End Function
         你會發現它多彈出一個視窗出來,基本上這個Form1是繼承你原來的Form1 Class新的Instance
         於是得到一個結論,如果在多執緒環境要從 Module回去Call主畫面的方法時,要將Form物件的參照傳進來  
         所以程式要改成
         Public Function MSG(ByVal item As Integer, ByRef obj As Form1)
            Select Case item
                Case 1
                    obj.AddList("test1")
                Case 2
                    obj.AddList("test2")
                Case 3
                    obj.AddList("test3")
            End Select       
        End Function
        主畫面的呼叫則要改成
         MSG(TextBox1.Text,me)
     ================================================
    說實在話,我雖然常在寫多執緒的程式,也沒這樣搞過,因為實在不習慣從其它執行緒直接控制主畫面的控制項.
    Module中最好還是專心處理值的問題,不論是變數還是物件,再把值回傳給主畫面的程式就好.
    用Module這樣去控制主畫面控制項,感覺有點不是那麼的容易除錯.
    • 已標示為解答 eblue 2009年4月15日 上午 02:09
    2009年4月13日 上午 10:39
    版主
  • 也許是我半路出家.所以寫程式大多翻書或是自己試著做..在Module中去控制主畫面控制項的做法是由我自己學VB6那時的習慣,功能出來就好.反而沒去深究
    所以才想再重新進入VB2008也算重新學起
    感謝大大們熱心的幫小弟..
    新手上路
    • 已標示為解答 eblue 2009年4月15日 上午 02:09
    • 已取消標示為解答 eblue 2009年4月15日 上午 02:09
    2009年4月14日 上午 01:08
  • 在 Module 時做也沒啥不可以的,我就會把狀態列的說明放在 Module StartUp ,由 StartUp 宣告 MainForm ,再由 StartUp 呼叫 MainForm 的自訂方法,換專案的時候就不用改太多,在該專案的 MainForm 實做自定方法即可。

    不使用 Form1 是要考慮的,因為這是 Case by case ,而且這是 vb 專用的隱藏宣告,在 VB2002/2003 中,拿掉 VB6 內建的隱藏宣告,也就是說,並沒有任何實體被宣告為:
    Public Form1 As New Form1

    到了 VB2005 想要大量吸引 VB6 的開發者,才又恢復這個機制。

    程式怎樣寫比較好,可以討論,但是沒有規定一定要怎樣寫,有這種規定的話,那會加在編譯器限制。
    避免在 Module 中使用特定變數名,是提高 Function 的可重複利用價值,但並不是必然的,很多程式碼本身只有給這個專案專用時,也不需要考慮可重複性。

    在 VB2002/VB2003 中,不同的執行緒建立的控制項也可以直接操作,但到 VB2005 以後,加入安全管制,非建立控制項的執行緒不能變更控制項內容,必須使用委派,當然可以用 BackgroundWorker 控制項,但有些情況用 BackgroundWorker 控制項會出問題,比如說在 socket 作用下或是非可視環境,所以我比較偏向直接用委派。這裡面有個要注意的,要是用 work thread 建立控制項,而 work thread 終結後,又該怎麼辦?所以一般來說我會保留讓 Main thread 來建立控制項 (gdi thread) ,這樣就可以委派讓主執行緒來做控制項內容變更。

    比如說上面說的狀態列變更,我把它寫在 Module 內,則不管我是否有建立新的子執行緒 (work thread) ,我都可以呼叫同一個副程式來變更狀態列的說明,並沒規定在主執行緒就不能呼叫委派,經過測試證明也的確可以這樣做,那麼寫好的 Module 在不同專案只要修改:
    Public MainForm As New formname
    斜線部分的 formname ,並在該 formname 建立相容的方法即可。

    程式碼沒有絕對的寫法,自己爽就好。

    我比較有意見的是,貼了程式碼確沒有標示錯誤行號跟錯誤訊息,這對於網路討論才是大問題。


    論壇是網友平等互助 保證解答請至 微軟技術支援服務
    2009年4月14日 上午 06:01