我把一个小测试程序放在一起,了解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
部分的搜索结果,而且我的做法看起来是正确的,但它不起作用。
有人可以告诉我,有什么不对?
再次感谢您的帮助。
如果我不在创建任务时使用'TaskScheduler.FromCurrentSynchronizationContext'并在我的表单的代码中使用InvokeRequired',程序根本没有显示任何进度,表格只打开并显示等待光标,就是这样。这是我第一个问题的主题,如何在不阻塞UI线程的情况下运行任务。 – Nostromo 2013-04-11 07:14:06
我已经尝试过'BackgroundWorker'并且运行了它,但是 1.我不想使用BackgroundWorker,因为它是一个组件,因此需要一个表单才能正常工作,并且此表单不可重用,因为所有代码和事件处理发生在表单中。 2.我想了解多线程,因此想要了解为什么它按照它的方式工作,即使它不是我希望它工作的方式。 那么,有什么建议怎么做才能让我的两个'任务'并行运行**和**显示进度? – Nostromo 2013-04-11 07:21:36
@Nostromo - 如果你使用'InvokeRequired'(和'Invoking'),但你的UI线程仍然在WaitAll()调用中,那么不,它不会工作 - UI线程*不能*调用'WaitAll()' - 它必须可以自由地处理'Invoke'请求。 – 2013-04-11 07:23:46