2013-04-11 83 views
1

我把一個小測試程序放在一起,瞭解VB.net中的多線程。經過大量的研究,嘗試和失敗,我得到了我的程序運行,至少有一點。爲什麼我的任務不能並行運行?

現在它的表現很奇怪,我無法解釋爲什麼。也許你可以告訴我,我的程序出了什麼問題,爲什麼它的行爲方式和/或我必須改變的方式才能使它按照本應該工作的方式工作。

我的程序包含一個類(TestClass),它模擬我想要在不同線程上執行的長時間運行的任務,進度窗體(Form1)顯示來自長時間運行的任務和主程序的進度消息,將這兩者放在一起。

這裏是我的代碼:

Public Class Form1 

Public Sub AddMessage1(ByVal message As String) 
    If String.IsNullOrEmpty(message) Then 
    Exit Sub 
    End If 

    Me.ListBox1.Items.Add(message) 
    Me.ListBox1.SelectedIndex = Me.ListBox1.Items.Count - 1 
    Application.DoEvents() 
End Sub 

Public Sub AddMessage2(ByVal message As String) 
    If String.IsNullOrEmpty(message) Then 
    Exit Sub 
    End If 

    Me.ListBox2.Items.Add(message) 
    Me.ListBox2.SelectedIndex = Me.ListBox2.Items.Count - 1 
    Application.DoEvents() 
End Sub 

End Class 

的TestClass

Imports System.Threading 

Public Class TestClass 

    Public Event ShowProgress(ByVal message As String) 

    Private _milliSeconds As UShort 
    Private _guid As String 

    Public Sub New(milliSeconds As UShort, ByVal guid As String) 
    _milliSeconds = milliSeconds 
    _guid = guid 
    End Sub 

    Public Function Run() As UShort 
    For i As Integer = 1 To 20 
     RaiseEvent ShowProgress("Run " & i) 
     Thread.Sleep(_milliSeconds) 
    Next i 

    Return _milliSeconds 
    End Function 

End Class 

Main過程包含兩個列表框(ListBox1中和ListBox2)

進展形式(Form1中) :

Imports System.Threading.Tasks 
Imports System.ComponentModel 

Public Class Start 
    Private Const MULTI_THREAD As Boolean = True 

    Public Shared Sub Main() 
    Dim testClass(1) As TestClass 
    Dim testTask(1) As Task(Of UShort) 
    Dim result(1) As UShort 

    testClass(0) = New TestClass(50, "Test1") 
    testClass(1) = New TestClass(200, "Test2") 

    Using frm As Form1 = New Form1 
     frm.Show() 

     AddHandler testClass(0).ShowProgress, AddressOf frm.AddMessage1 
     AddHandler testClass(1).ShowProgress, AddressOf frm.AddMessage2 

     If MULTI_THREAD Then 
     testTask(0) = Task(Of UShort).Factory.StartNew(Function() testClass(0).Run, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext) 
     testTask(1) = Task(Of UShort).Factory.StartNew(Function() testClass(1).Run, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext) 

     Task.WaitAll(testTask) 

     result(0) = testTask(0).Result 
     result(1) = testTask(1).Result 
     Else 
     result(0) = testClass(0).Run 
     result(1) = testClass(1).Run 
     End If 

     RemoveHandler testClass(0).ShowProgress, AddressOf frm.AddMessage1 
     RemoveHandler testClass(1).ShowProgress, AddressOf frm.AddMessage2 

     frm.Close() 
    End Using 

    MessageBox.Show("Result 1: " & result(0) & "; Result 2: " & result(1)) 
    End Sub 

End Class 

這個程序應該打開的進度表,並行運行兩個長時間運行的任務,表現出的兩項長時間運行的任務的進度消息,關閉進度表格當兩個長時間運行的任務都完成,顯示消息框與長時間運行任務的結果。

現在到我無法解釋的行爲:如果我運行此程序,它顯示在兩個列表框中的「運行1」,因此兩個任務開始運行,但只有第一個列表框被填充,並且只有當第一個列表框被填充(第一個任務完成),第二個列表框繼續填充。所以,這兩項任務都開始了,但第二項任務在繼續運行之前等待第一項任務完成。
但是,當我在我的主程序中註釋掉Task.WaitAll(testTask)時,這是相反的。它在兩個列表框中顯示「運行1」,然後第二個列表框被填充,並且僅當第二個列表框被填充(第二個任務完成)時,第一個列表框繼續運行。

爲什麼我的程序行爲如此奇怪,我該如何讓它同時運行我的兩個任務?

謝謝你的幫助解決這個一點神祕:-)


更新
由於唯一的答案迄今指出,它與我使用我刪除了SynchronizationContext做從我的主要過程的代碼:

Main過程

Imports System.Threading.Tasks 
Imports System.ComponentModel 

Public Class Start 
    Private Const MULTI_THREAD As Boolean = True 

    Public Shared Sub Main() 
    Dim testClass(1) As TestClass 
    Dim testTask(1) As Task(Of UShort) 
    Dim result(1) As UShort 

    testClass(0) = New TestClass(50, "Test1") 
    testClass(1) = New TestClass(200, "Test2") 

    Using frm As Form1 = New Form1 
     frm.Show() 

     AddHandler testClass(0).ShowProgress, AddressOf frm.AddMessage1 
     AddHandler testClass(1).ShowProgress, AddressOf frm.AddMessage2 

     If MULTI_THREAD Then 
     testTask(0) = Task(Of UShort).Factory.StartNew(Function() testClass(0).Run) 
     testTask(1) = Task(Of UShort).Factory.StartNew(Function() testClass(1).Run) 

     Task.WaitAll(testTask) 

     result(0) = testTask(0).Result 
     result(1) = testTask(1).Result 
     Else 
     result(0) = testClass(0).Run 
     result(1) = testClass(1).Run 
     End If 

     RemoveHandler testClass(0).ShowProgress, AddressOf frm.AddMessage1 
     RemoveHandler testClass(1).ShowProgress, AddressOf frm.AddMessage2 

     frm.Close() 
    End Using 

    MessageBox.Show("Result 1: " & result(0) & "; Result 2: " & result(1)) 
    End Sub 

End Class 

爲了避免因爲非法跨線程調用的InvalidOperationException,我改變形式的代碼如下:

進展形式(Form1中)含有兩個列表框(ListBox1中和ListBox2):

Public Delegate Sub ShowProgressDelegate(ByVal message As String) 

Public Class Form1 

Public Sub AddMessage1(ByVal message As String) 
    If String.IsNullOrEmpty(message) Then 
    Exit Sub 
    End If 

    Debug.Print("out List 1: " & message) 

    If Me.InvokeRequired Then 
    Me.BeginInvoke(New ShowProgressDelegate(AddressOf Me.AddMessage1), message) 
    Else 
    Debug.Print("in List 1: " & message) 

    Me.ListBox1.Items.Add(message) 
    Me.ListBox1.SelectedIndex = Me.ListBox1.Items.Count - 1 
    Application.DoEvents() 
    End If 
End Sub 

Public Sub AddMessage2(ByVal message As String) 
    If String.IsNullOrEmpty(message) Then 
    Exit Sub 
    End If 

    Debug.Print("out List 2: " & message) 

    If Me.InvokeRequired Then 
    Me.BeginInvoke(New ShowProgressDelegate(AddressOf Me.AddMessage2), message) 
    Else 
    Debug.Print("in List 2: " & message) 

    Me.ListBox2.Items.Add(message) 
    Me.ListBox2.SelectedIndex = Me.ListBox2.Items.Count - 1 
    Application.DoEvents() 
    End If 
End Sub 

End Class 

我剛剛添加了一些Debug.Prints用於調試和InvokeRequired部件。

現在這兩個任務並行運行(我知道是因爲Debug.Print調用了「out List」消息),但是我的進度消息沒有顯示在列表框中,相應的代碼從未得到執行(Debug.Print調用「在列表中「消息不會顯示在調試窗口中)。

我以正確的方式執行InvokeRequired部分的搜索結果,而且我的做法看起來是正確的,但它不起作用。
有人可以告訴我,有什麼不對?

再次感謝您的幫助。

回答

2

您正在告訴系統在當前同步上下文中運行任務。

TaskScheduler.FromCurrentSynchronizationContext 

在這種情況下,該同步上下文是UI線程 - 其中只有一個。所以這些任務將排隊等待,直到它們可以在UI線程上運行 - 只有當您輸入WaitAll()調用時纔可用(因爲WaitAll的規則說,如果當前線程適合運行一個或多個任務(它是)並且那些相同的任務還沒有開始(他們不能這樣做),當前線程可以直接執行一個或多個這些任務)

可能要求任務在一個不同同步上下文(或線程池),但然後遇到不同的問題 - 在線程正在引發的事件中,您正在與UI元素進行交互。

也許你應該看看BackgroundWorker類,它在一個單獨的線程上運行代碼,但專門爲報告進度返回到UI/Foreground線程而構建。

+0

如果我不在創建任務時使用'TaskScheduler.FromCurrentSynchronizationContext'並在我的表單的代碼中使用InvokeRequired',程序根本沒有顯示任何進度,表格只打開並顯示等待光標,就是這樣。這是我第一個問題的主題,如何在不阻塞UI線程的情況下運行任務。 – Nostromo 2013-04-11 07:14:06

+0

我已經嘗試過'BackgroundWorker'並且運行了它,但是 1.我不想使用BackgroundWorker,因爲它是一個組件,因此需要一個表單才能正常工作,並且此表單不可重用,因爲所有代碼和事件處理髮生在表單中。 2.我想了解多線程,因此想要了解爲什麼它按照它的方式工作,即使它不是我希望它工作的方式。 那麼,有什麼建議怎麼做才能讓我的兩個'任務'並行運行**和**顯示進度? – Nostromo 2013-04-11 07:21:36

+0

@Nostromo - 如果你使用'InvokeRequired'(和'Invoking'),但你的UI線程仍然在WaitAll()調用中,那麼不,它不會工作 - UI線程*不能*調用'WaitAll()' - 它必須可以自由地處理'Invoke'請求。 – 2013-04-11 07:23:46

0

經過大量的搜索和嘗試,我想出了一個解決方案。它不漂亮,但它的工作原理。

在我看來,Task.WaitAll()不僅等待交付任務完成,而且它被調用的任務。
所以,從

... 
Task.WaitAll(testTask) 
... 

改變我的主要程序代碼

... 
Do While Not Task.WaitAll(testTask, 100) 
    Application.DoEvents 
Loop 
... 

的伎倆,我的進度消息得到在列表框所示。

相關問題