我整理了一个小测试程序来了解 VB.net 中的多线程。经过大量的研究、尝试和失败后,我的程序至少运行了一点。
现在它的行为很奇怪,我无法解释原因。也许你可以告诉我,我的程序有什么问题,为什么它的行为方式和/或我必须改变什么才能让它按照它应该的方式工作。
我的程序包含一个类(TestClass),它模拟了我希望每个在不同线程上执行的长时间运行的任务,一个进度表(Form1),用于显示来自我的长时间运行的任务的进度消息和主程序,这两个一起。
这是我的代码:
进度表(Form1) 包含两个列表框(ListBox1 和 ListBox2):
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
测试类:
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
主要程序:
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
我正在使用的有关,因此我将其从主程序的代码中删除:
主要程序:
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
带有“in List”消息的调用不会' t 显示在调试窗口中)。
我用谷歌搜索了做这InvokeRequired
部分的正确方法,我做的方法似乎是正确的,但它不起作用。
有人可以告诉我,怎么了?
再次感谢你的帮助。