2013-04-11 109 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 
... 

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

相关问题