我整理了一个小测试程序,以了解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
识别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
主要程序:
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)
时,反之亦然。它显示了" Run 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
来电与#34;在列表"消息不会显示在调试窗口中。
我用Google搜索了InvokeRequired
部分的正确方法,我做的方式似乎是正确的,但它不起作用。
有人可以告诉我,有什么不对吗?
再次感谢您的帮助。
答案 0 :(得分:2)
您告诉系统在当前同步上下文中运行任务。
TaskScheduler.FromCurrentSynchronizationContext
在这种情况下,该同步上下文是UI线程 - 其中只有一个。因此,任务将排队,直到它们可以在UI线程上运行 - 这只有在您输入WaitAll()
调用时才可用(因为WaitAll
的规则说,如果当前线程适合运行一个或多个任务(它是)和那些相同的任务尚未启动(它们不能拥有),当前线程可以直接执行其中一个或多个任务)
你可以要求在不同的同步上下文(或线程池)上运行任务,但是你会遇到一个不同的问题 - 在事件中线程正在提升,你正在与UI元素进行交互。
也许您应该查看BackgroundWorker
类,它在单独的线程上运行代码,但专门用于将进度报告回UI / Foreground线程。
答案 1 :(得分:0)
经过大量的搜索和尝试,我想出了一个解决方案。它不漂亮,但它有效。
在我看来,Task.WaitAll()不仅要等待移交任务完成,还要阻止调用它的任务。
所以,从
...
Task.WaitAll(testTask)
...
到
...
Do While Not Task.WaitAll(testTask, 100)
Application.DoEvents
Loop
...
这个技巧和我的进度消息会显示在列表框中。