none
Thread.Join 問題 RRS feed

  • 問題

  • 我想要嘗試使用 Thread.Join 功能替壓縮 Access 資料庫時避開多條執行緒對同一個資料表做讀寫時發生錯誤,
    因為壓縮資料庫時必須獨占資料庫才有辦法壓縮,當正在壓縮過程中有其他執行緒愈讀寫該資料庫時就會發生衝突。

    Thread.Join 功能我想應用在備份資料庫時,
    資料庫用 Insert 語句從 [C:\a.mdb].資料表1 複製到 [C:\b.mdb].資料表1,
    刪除 [C:\a.mdb].資料表1 所有內容,
    壓縮 [C:\a.mdb],

    我想把壓縮資料獨立出來,Thread.Join 給其他會讀寫該資料庫的執行緒中希望可以解決衝突問題。

    照著 [網路文章] 想修改成 VB.NET WinForm 版,有點修改失敗,我的語法有哪邊修改錯了?
    我有改成 VB.NET 主控台版試試看,有成功。

    VB.NET WinForm 版

        Sub ThreadProc()
            For i = 0 To 9
                myUI(i, ListBox1)
                Thread.Sleep(100)
            Next
        End Sub
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            ListBox1.Items.Add("主執行緒一開始")
    
            Dim t As New Thread(AddressOf ThreadProc)
            t.IsBackground = True
            t.Start()
    
            For i = 1 To 4
                ListBox1.Items.Add("主執行緒一工作")
                Thread.Sleep(10)
            Next
    
            ListBox1.Items.Add("啟動執行緒二,執行緒一等待執行緒二結束")
            t.Join()
            ListBox1.Items.Add("執行緒二結束,執行緒一結束")
        End Sub
    
    
        Private Delegate Sub myUICallBack(ByVal myStr As String, ByVal c As Control)
        Private Sub myUI(ByVal myStr As String, ByVal c As Control)
            If InvokeRequired Then
                Dim myUpdate As myUICallBack = New myUICallBack(AddressOf myUI)  
                BeginInvoke(myUpdate, myStr, c)          
            Else
                CType(c, ListBox).Items.Add(myStr)           
            End If
        End Sub

    VB.NET 主控台版

    Module Module1
    
        Public Sub ThreadProc()
            For i = 0 To 9
                Console.WriteLine("ThreadProc: {0}, {1}", i, Thread.CurrentThread.ThreadState)
                Thread.Sleep(100)
            Next
        End Sub
    
        Sub Main()
            Console.WriteLine("Main thread: Start a second thread.")
            Dim t As Thread = New Thread(AddressOf ThreadProc)
            t.Start()
    
            For i = 0 To 3
                Console.WriteLine("Main thread: do some work")
                Thread.Sleep(1)
            Next
            Console.WriteLine("Main thread: Call Join(), to wait until ThreadProc ends.")
            t.Join()
            Console.WriteLine("Main thread: ThreadProc.Join has returned. Press Enter to end program.")
    
            Console.ReadLine()
        End Sub
    
    End Module

    主控台的結果和網路文章的幾乎一樣,





    • 已編輯 C.Kevin 2012年12月21日 上午 08:47
    2012年12月21日 上午 08:09

解答

  • Hi C.Kevin,

    问题在于你使用了BeginInvoke,建议这样修改(把这段代码注视掉,看我怎么注释的):

    然后重新编译并且直接运行(Ctrl+F5):

    因为BeginInvoke方法是异步的——这个意味着调用此方法之后不会立即执行这个方法,而是继续执行下面的程序。从你的代码来看,Join方法虽然阻碍了WinForm主线程,但是Join方法里边调用了BeginInvoke,结果异步执行。所以Join方法很快执行完毕了所有的BeginInvoke方法,主线程继续,然后BeginInvoke才被执行。

    Imports System.Threading
     
    Public Class Form1
        Private Delegate Sub myUICallBack(ByVal myStr As String)
        Sub ThreadProc()
            For i = 1 To 10
                myUI(i.ToString())
                Thread.Sleep(20)
            Next
        End Sub
     
        Private Sub myUI(ByVal myStr As String)
            If InvokeRequired Then
                ListBox1.Items.Add("子线程:" + myStr)
                'ListBox1.BeginInvoke(New MethodInvoker(Sub()
                '                                           ListBox1.Items.Add("子线程:" + myStr)
                '                                       End Sub))
            Else
                ListBox1.Items.Add(myStr)
            End If
        End Sub
     
        Private Sub Button1_Click(sender As Object, e As EventArgsHandles Button1.Click
            ListBox1.Items.Add("主執行緒一開始")
     
     
            Dim t As New Thread(AddressOf ThreadProc)
            t.IsBackground = True
            t.Start()
     
            For i = 1 To 4
                myUI(i & ":主執行緒一工作")
                Thread.Sleep(10)
            Next
     
            ListBox1.Items.Add("啟動執行緒二,執行緒一等待執行緒二結束")
            t.Join()
            ListBox1.Items.Add("執行緒二結束,執行緒一結束")
     
        End Sub
    End Class

    帮助一起改进论坛质量?提交你的意见于此。
    我的博客园
    慈善点击,点击此处
    和谐拯救危机,全集下载,净化人心

    • 已提議為解答 亂馬客 2012年12月22日 上午 04:54
    • 已標示為解答 C.Kevin 2012年12月27日 上午 06:02
    2012年12月22日 上午 04:31
  • 如同前人所言, 是因為你用了 BeginInvoke, 程式改成以下再試一次, 看是不是你要的結果,

        Sub ThreadProc(ByVal obj As Object)
            For i = 0 To 9
                myUI(i, ListBox1)
                Thread.Sleep(100)
            Next
            Dim v() As Integer = obj
            v(0) = 1
        End Sub
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            ListBox1.Items.Add("主執行緒一開始")
    
            Dim t As New Thread(AddressOf ThreadProc)
            t.IsBackground = True
            Dim v() As Integer = {0}
            t.Start(v)
    
            For i = 1 To 4
                ListBox1.Items.Add("主執行緒一工作")
                Thread.Sleep(10)
            Next
    
            ListBox1.Items.Add("啟動執行緒二,執行緒一等待執行緒二結束")
            While (v(0) = 0)
                Application.DoEvents()
            End While
            ListBox1.Items.Add("執行緒二結束,執行緒一結束")
        End Sub
    
    
        Private Delegate Sub myUICallBack(ByVal myStr As String, ByVal c As Control)
        Private Sub myUI(ByVal myStr As String, ByVal c As Control)
            If InvokeRequired Then
                Dim myUpdate As myUICallBack = New myUICallBack(AddressOf myUI)
                Invoke(myUpdate, myStr, c)
            Else
                CType(c, ListBox).Items.Add(myStr)
            End If
        End Sub

    用 control 的 invoke 或 begininvoke, 都是切到 control 所在的 message loop 的執行緒去執行,

    所以, 用 begininvoke 會把你的委派丟到 form 的 thread 去排隊, 因為是非同步的, 所以馬上 return 回原本的 thread 繼續執行,

    而這時你 form 的 thread 還在執行你的 button1_click, 所以你丟去排隊的 0 ~ 9 會等你的 button1_click 跑完才執行到....

    改用 invoke 後, 因為不是非同步的, 所以, 你在丟出 0 這個任務時, 會等他切到 form 的 thread 執行完再回來, 再進行下一圈,

    此時如果呼叫 join , 當然就是死結....button1_click 在等 thread 跑完, thread invoke 後在等 button1_click 跑完去執行他的任務,

    在 join 前, 加個 doevents, 就可以讓他去幫你處理掉一個排隊的任務....

    弄個變數看一下 thread 跑完了沒, 就知道要 doevents 到何時, 最後 thread 結束了, 有沒有 join 都沒差了


    • 已標示為解答 C.Kevin 2012年12月28日 上午 05:35
    2012年12月27日 下午 12:48

所有回覆

  • 在WinForm用Process.start()去呼叫主控抬應用程式。

    理直氣和,切記。

    http://blog.kkbruce.net

    2012年12月21日 上午 08:44
  • 抱歉,我忘記附上圖片了..

    是執行結果不如預期。
    ListBox 顯示的字串順序不對,也有點當機的感覺,須要等所有程式跑完視窗才會動作。


    • 已編輯 C.Kevin 2012年12月21日 上午 08:48
    2012年12月21日 上午 08:45
  • 你把 Thread.Join 說明再看一遍,看看中文用哪個詞,然後你的答案就出現了。

    論壇是網友平等互助 保證解答請至 微軟技術支援服務


    提問時,錯誤情境描述與錯誤訊息很重要,情境描述包含你做了什麼,預期的結果與實際發生的結果。一個最爛的問法範例:「我的電腦電腦怎麼不能開機?」誰知道你家是不是沒電還是你根本找不到電源鈕。

    2012年12月21日 下午 03:54
  • Hi C.Kevin,

    问题在于你使用了BeginInvoke,建议这样修改(把这段代码注视掉,看我怎么注释的):

    然后重新编译并且直接运行(Ctrl+F5):

    因为BeginInvoke方法是异步的——这个意味着调用此方法之后不会立即执行这个方法,而是继续执行下面的程序。从你的代码来看,Join方法虽然阻碍了WinForm主线程,但是Join方法里边调用了BeginInvoke,结果异步执行。所以Join方法很快执行完毕了所有的BeginInvoke方法,主线程继续,然后BeginInvoke才被执行。

    Imports System.Threading
     
    Public Class Form1
        Private Delegate Sub myUICallBack(ByVal myStr As String)
        Sub ThreadProc()
            For i = 1 To 10
                myUI(i.ToString())
                Thread.Sleep(20)
            Next
        End Sub
     
        Private Sub myUI(ByVal myStr As String)
            If InvokeRequired Then
                ListBox1.Items.Add("子线程:" + myStr)
                'ListBox1.BeginInvoke(New MethodInvoker(Sub()
                '                                           ListBox1.Items.Add("子线程:" + myStr)
                '                                       End Sub))
            Else
                ListBox1.Items.Add(myStr)
            End If
        End Sub
     
        Private Sub Button1_Click(sender As Object, e As EventArgsHandles Button1.Click
            ListBox1.Items.Add("主執行緒一開始")
     
     
            Dim t As New Thread(AddressOf ThreadProc)
            t.IsBackground = True
            t.Start()
     
            For i = 1 To 4
                myUI(i & ":主執行緒一工作")
                Thread.Sleep(10)
            Next
     
            ListBox1.Items.Add("啟動執行緒二,執行緒一等待執行緒二結束")
            t.Join()
            ListBox1.Items.Add("執行緒二結束,執行緒一結束")
     
        End Sub
    End Class

    帮助一起改进论坛质量?提交你的意见于此。
    我的博客园
    慈善点击,点击此处
    和谐拯救危机,全集下载,净化人心

    • 已提議為解答 亂馬客 2012年12月22日 上午 04:54
    • 已標示為解答 C.Kevin 2012年12月27日 上午 06:02
    2012年12月22日 上午 04:31
  • Thread.Join封鎖呼叫執行緒直到執行緒終止為止,但仍繼續執行標準的 COM 與 SendMessage 幫浦作業。
    我不是很懂 
    標準的 COM 是什麼意思,SendMessage 幫浦作業 可能是指不是真的會完全封鎖,少部分功能還是會往下執行,像是 MessageBox 不在封鎖範圍。

     

    我想我真的不曉得怎麼將程式修改成大大們所說的那樣,我把 BeginInvoke 改成 Invoke ,然後程式執行起來有點像死結,視窗無反應,

    Imports System.Threading
    
    Public Class Form1
        Sub ThreadProc()
            For i = 0 To 9
                myUI(i)
                Thread.Sleep(100)
            Next
        End Sub
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            ListBox1.Items.Add("主執行緒一開始")
    
            Dim t As New Thread(AddressOf ThreadProc)
            t.IsBackground = True
            t.Start()
    
            For i = 1 To 4
                ListBox1.Items.Add("主執行緒一工作")
                Thread.Sleep(10)
            Next
    
            ListBox1.Items.Add("啟動執行緒二,執行緒一等待執行緒二結束")
            t.Join()
            ListBox1.Items.Add("執行緒二結束,執行緒一結束")
        End Sub
    
    
        Private Delegate Sub myUICallBack(ByVal myStr As String)
        Private Sub myUI(ByVal myStr As String)
            If InvokeRequired Then
                Dim myUpdate As myUICallBack = New myUICallBack(AddressOf myUI)
                Invoke(myUpdate, myStr)
            Else
                ListBox1.Items.Add(myStr)
            End If
        End Sub
    End Class
    
     

     

    編程志愿者 大大的程式碼 Ctrl + F5 成果順利執行。
    我把 Join 拿掉,程式雖然沒有照著想要的結果執行,但視窗卻沒因為程式未結束而 Block 住,加上 Join 雖然有出現想要的結果但視窗要等程式執行完畢才有回應,
    有辦法解嗎? 是不是因為 Join 封鎖的關係?

    因為 F5 會出現 跨執行緒作業無效 的錯誤訊息,於是我修改了程式,執行後呈現的狀況與上面的程式碼一樣。

    Imports System.Threading
    
    Public Class Form1
        Private Delegate Sub myUICallBack(ByVal myStr As String)
        Sub ThreadProc()
            For i = 1 To 10
                myUI(i.ToString())
                Thread.Sleep(100)
            Next
        End Sub
    
        Private Sub myUI(ByVal myStr As String)
            If ListBox1.InvokeRequired Then
                Dim myUpdate As myUICallBack = New myUICallBack(AddressOf myUI)
                'ListBox1.Items.Add("子线程:" + myStr)
                'ListBox1.BeginInvoke(New MethodInvoker(Sub()
                '                                           ListBox1.Items.Add("子线程:" + myStr)
                '                                       End Sub))
                BeginInvoke(myUpdate, New Object() {myStr})
            Else
                ListBox1.Items.Add(myStr)
            End If
        End Sub
    
        Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
            ListBox1.Items.Add("主執行緒一開始")
    
    
            Dim t As New Thread(AddressOf ThreadProc)
            t.IsBackground = True
            t.Start()
    
            For i = 1 To 4
                myUI(i & ":主執行緒一工作")
                Thread.Sleep(10)
            Next
    
            ListBox1.Items.Add("啟動執行緒二,執行緒一等待執行緒二結束")
    
            t.Join()
            ListBox1.Items.Add("執行緒二結束,執行緒一結束")
    
        End Sub
    End Class

    2012年12月25日 上午 01:29
  • 请严格按照我的代码,仔细看——我注释的部分以及增加的部分。

    绝对不会死掉的,我亲自测试过的。


    帮助一起改进论坛质量?提交你的意见于此。
    我的博客园
    慈善点击,点击此处
    和谐拯救危机,全集下载,净化人心

    2012年12月25日 上午 01:38
  • To 編成志愿者:

    遵照您的代碼再用 Ctrl + F5 執行完全沒有問題與錯誤,只有當我使用 F5 時才出錯,
    我在網路上有查找 Ctrl + F5 與 F5 功能差別,找到的結果是 Ctrl + F5 無進入除錯模式,F5 有進入除錯模式,才想說修改看看。

    2012年12月25日 上午 02:38
  • 2012年12月25日 上午 08:41
  • 其實我不是很懂為什麼使用 Ctrl + F5 的方式執行,和往常除錯的方式不一樣,可以跟我說為什麼嗎?

    我不是很懂委派,於是我到網路上查了資料,網路上說 Delegate 是用來儲存有誰需要它來執行東西 (也許是同時有2個人需要它來做某樣事情,然後先進先出的處理),
    或是接收(監聽)呼叫的概念,
    我知道下面的2組程式碼做的事情是一樣,但我很難去把它們理解成一樣的,應該要怎麼去理解 Delegate?

    程式一:

    Public Class Form1
    
        Delegate Function StrMerge(ByVal str1 As String, ByVal str2 As String) As String
    
        Sub My_Show(ByVal My_comparemethod As StrMerge)
            MessageBox.Show(My_comparemethod.Invoke("Str1", "Str2"))
        End Sub
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim My_StrMerge As StrMerge = AddressOf Str1BeforeStr2
            My_Show(My_StrMerge)
        End Sub
    
        Public Function Str1BeforeStr2(ByVal S1 As String, ByVal S2 As String)
            Return S1 + " And " + S2
        End Function
    End Class

    程式二:

        Private Delegate Sub myUICallBack(ByVal myStr As String, ByVal c As Control)
        Private Sub myUI(ByVal myStr As String, ByVal c As Control)
            If InvokeRequired Then
                Dim myUpdate As myUICallBack = New myUICallBack(AddressOf myUI)
                BeginInvoke(myUpdate, myStr, c)
            Else           
                c.Text = myStr   
            End If
        End Sub


    • 已編輯 C.Kevin 2012年12月25日 上午 09:29
    2012年12月25日 上午 09:27
  •   

    第一段代碼:是先定義一個委託的實例,然後把這個實例傳入一個需要委託的參數之後調用委託,那麼自然執行對應的方法。

    第二段代碼:該代碼可能是主執行緒調用,也可以是子執行緒調用。因而需要用RequiredInvoke來進一步判斷,然後使用非同步方法調用。

    帮助一起改进论坛质量?提交你的意见于此。
    我的博客园
    慈善点击,点击此处
    和谐拯救危机,全集下载,净化人心



    2012年12月26日 上午 05:56
  • To 編程志愿者:

    對委派我有一點概念了,本來想用 Thread.join 來排除當有壓縮 Access 需求,同時有 Access 讀寫需求時會造成資料庫處理的衝突,頻頻跳出 Exception,
    但使用後好像沒有改善。

    謝謝 編程志愿者,我另開主題來詢問大家的建議好了。



    • 已編輯 C.Kevin 2012年12月27日 上午 06:06
    2012年12月27日 上午 06:02
  • 如同前人所言, 是因為你用了 BeginInvoke, 程式改成以下再試一次, 看是不是你要的結果,

        Sub ThreadProc(ByVal obj As Object)
            For i = 0 To 9
                myUI(i, ListBox1)
                Thread.Sleep(100)
            Next
            Dim v() As Integer = obj
            v(0) = 1
        End Sub
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            ListBox1.Items.Add("主執行緒一開始")
    
            Dim t As New Thread(AddressOf ThreadProc)
            t.IsBackground = True
            Dim v() As Integer = {0}
            t.Start(v)
    
            For i = 1 To 4
                ListBox1.Items.Add("主執行緒一工作")
                Thread.Sleep(10)
            Next
    
            ListBox1.Items.Add("啟動執行緒二,執行緒一等待執行緒二結束")
            While (v(0) = 0)
                Application.DoEvents()
            End While
            ListBox1.Items.Add("執行緒二結束,執行緒一結束")
        End Sub
    
    
        Private Delegate Sub myUICallBack(ByVal myStr As String, ByVal c As Control)
        Private Sub myUI(ByVal myStr As String, ByVal c As Control)
            If InvokeRequired Then
                Dim myUpdate As myUICallBack = New myUICallBack(AddressOf myUI)
                Invoke(myUpdate, myStr, c)
            Else
                CType(c, ListBox).Items.Add(myStr)
            End If
        End Sub

    用 control 的 invoke 或 begininvoke, 都是切到 control 所在的 message loop 的執行緒去執行,

    所以, 用 begininvoke 會把你的委派丟到 form 的 thread 去排隊, 因為是非同步的, 所以馬上 return 回原本的 thread 繼續執行,

    而這時你 form 的 thread 還在執行你的 button1_click, 所以你丟去排隊的 0 ~ 9 會等你的 button1_click 跑完才執行到....

    改用 invoke 後, 因為不是非同步的, 所以, 你在丟出 0 這個任務時, 會等他切到 form 的 thread 執行完再回來, 再進行下一圈,

    此時如果呼叫 join , 當然就是死結....button1_click 在等 thread 跑完, thread invoke 後在等 button1_click 跑完去執行他的任務,

    在 join 前, 加個 doevents, 就可以讓他去幫你處理掉一個排隊的任務....

    弄個變數看一下 thread 跑完了沒, 就知道要 doevents 到何時, 最後 thread 結束了, 有沒有 join 都沒差了


    • 已標示為解答 C.Kevin 2012年12月28日 上午 05:35
    2012年12月27日 下午 12:48
  • 謝謝 LongHairPan,這是我想要的結果,但是程式只有一個簡單回圈,CPU 使用率卻超高,是什麼原因造成這樣?

    請問您程式中這一部分是不是跟 ByRef 意思一樣呢?
    可是 ThreadProc 裡的參數又是 ByVal。

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim v() As Integer = {0}
            ThreadProc(v)
            Label1.Text = v(0)
        End Sub
    
        Sub ThreadProc(ByVal obj As Object)
            Dim v() As Integer = obj
            v(0) = 1
        End Sub




    • 已編輯 C.Kevin 2012年12月28日 上午 06:05
    2012年12月28日 上午 05:41
  • cpu 的問題, 是 while 迴圈導致, 你在 application.doevents 前或後, 加一行 thread.sleep(1) 即可,

    array 是 ref type ,

    你可以參考 msdn,

    http://msdn.microsoft.com/zh-tw/library/t63sy5hs(v=vs.80).aspx

    至於為什麼用 byval 不用 byref ?

    可以看 msdn 的 Thread 的建構式及 ParameterizedThreadStart  delegate,

    http://msdn.microsoft.com/zh-tw/library/1h2f2459(v=vs.90).aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1

    http://msdn.microsoft.com/zh-tw/library/system.threading.parameterizedthreadstart(v=vs.90).aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1

    2012年12月28日 上午 09:32
  • 謝謝 LongHairPan。
    2012年12月31日 上午 05:35