.net 为什么我的任务不';我们不能并行吗?

.net 为什么我的任务不';我们不能并行吗?,.net,vb.net,multithreading,task,.net,Vb.net,Multithreading,Task,我编写了一个小测试程序来学习VB.net中的多线程。经过大量的研究、尝试和失败,我的程序至少运行了一点 现在它的行为很奇怪,我无法解释原因。也许你可以告诉我,我的程序出了什么问题,为什么它的行为方式是这样的,和/或我必须改变什么才能让它按预期的方式工作 我的程序包含一个类(TestClass),它模拟我希望在不同线程上执行的长时间运行的任务,一个进度表(Form1),显示来自长时间运行任务的进度消息,以及将这两个任务放在一起的主过程 这是我的密码: 进度表(表格1),包含两个列表框(列表框1和列

我编写了一个小测试程序来学习VB.net中的多线程。经过大量的研究、尝试和失败,我的程序至少运行了一点

现在它的行为很奇怪,我无法解释原因。也许你可以告诉我,我的程序出了什么问题,为什么它的行为方式是这样的,和/或我必须改变什么才能让它按预期的方式工作

我的程序包含一个类(TestClass),它模拟我希望在不同线程上执行的长时间运行的任务,一个进度表(Form1),显示来自长时间运行任务的进度消息,以及将这两个任务放在一起的主过程

这是我的密码:

进度表(表格1),包含两个列表框(列表框1和列表框2):

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
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
主程序

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
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
此程序应打开进度表,并行运行两个长时间运行的任务,显示两个长时间运行任务的进度消息,在两个长时间运行的任务完成后关闭进度表,并显示包含长时间运行任务结果的消息框

现在,对于我无法解释的行为:如果我运行这个程序,它会在两个列表框中显示“run 1”,因此两个任务都开始运行,但只有第一个列表框被填充,并且只有当第一个列表框被填充(第一个任务完成)时,第二个列表框才会继续被填充。因此,两个任务都将启动,但第二个任务将等待第一个任务完成后再继续运行。
但是,当我注释掉主过程中的
Task.WaitAll(testTask)
时,情况正好相反。它在两个列表框中都显示“运行1”,然后第二个列表框被填充,只有当第二个列表框被填充(第二个任务完成)时,第一个列表框才继续运行

为什么我的程序行为如此奇怪,我如何使它同时运行我的两个任务

谢谢你帮我解开这个小谜团:-)


更新
由于到目前为止唯一的答案表明它与我正在使用的
SynchronizationContext
有关,因此我将其从我的主要过程的代码中删除:

主程序

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
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
,我将表单的代码更改如下:

进度表(表格1),包含两个列表框(列表框1和列表框2):

我刚刚添加了一些用于调试的Debug.Prints和
invokererequired
部分

现在两个任务并行运行(我知道是因为
Debug.Print
调用带有“out List”消息),但是我的进度消息不会显示在列表框中,相应的代码永远不会执行(带有“in List”消息的
Debug.Print
调用不会显示在调试窗口中)

我在谷歌上搜索了执行
invokererequired
部分的正确方法,我这样做似乎是正确的,但不起作用。
谁能告诉我,怎么了


再次感谢您的帮助。

您告诉系统在当前同步上下文中运行任务

TaskScheduler.FromCurrentSynchronizationContext
在本例中,同步上下文是UI线程,其中只有一个线程。因此,任务将排队等待,直到它们可以在UI线程上运行—这只有在您输入
WaitAll()
调用时才可用(因为
WaitAll
的规则规定,如果当前线程适合运行一个或多个任务(它是),而这些相同的任务尚未启动(它们不能启动),当前线程可以直接执行其中一个或多个任务)

您可以要求在不同的同步上下文(或线程池)上运行任务,但随后您会遇到不同的问题—在线程引发的事件中,您正在与UI元素交互


也许你应该研究一下这个类,它在一个单独的线程上运行代码,但是它是专门为向UI/前台线程报告进度而构建的。

经过大量的搜索和尝试,我想出了一个解决方案。虽然不漂亮,但很管用

在我看来,Task.WaitAll()不仅等待移交的任务完成,而且还阻止了调用它的任务。
因此,将我的主过程的代码从

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


技巧和我的进度消息是否会显示在列表框中。

如果我不移交任务调度器。从CurrentSynchronizationContext创建任务并在我表单的代码中使用调用所需的
时,程序根本不显示进度,表单只会打开并显示等待光标,仅此而已。这是我这里第一个问题的主题,如何在不阻塞UI线程的情况下运行任务。我已经尝试了
BackgroundWorker
并使其运行,但是1。我不想使用
BackgroundWorker
,因为它是一个组件,因此需要一个表单才能正常工作,而这个表单是不可重用的,因为所有的代码和事件处理都发生在表单中。2.我想学习多线程,因此想理解为什么它是这样工作的,即使它不是我想要的工作方式。那么,有什么建议可以让我的两个
任务
并行运行并显示进度吗?@Nostromo-如果您使用的是
invokererequired
(和
Invoking
),但您的UI线程位于
WaitAll()调用中,那么不,它不会工作-UI线程无法调用
WaitAll()
-它必须免费为
调用
请求提供服务。那么,您有什么建议可以在我的代码中进行更改以使其按我希望的方式运行吗?