1

VB.net のマルチスレッドについて学習するための小さなテスト プログラムを作成しました。多くの調査、試行、失敗の後、少なくとも少しはプログラムを実行することができました。

今では奇妙な振る舞いをしており、その理由を説明することはできません. 私のプログラムの何が問題なのか、なぜそのように動作するのか、および/または動作するはずの方法で動作させるために何を変更する必要があるのか​​ を教えてください。

私のプログラムには、それぞれ別のスレッドで実行する長時間実行タスクをシミュレートするクラス (TestClass)、長時間実行タスクからの進行状況メッセージを表示する進行状況フォーム (Form1)、およびこれら 2 つを配置するメイン プロシージャが含まれています。一緒。

これが私のコードです:

2 つのリストボックス (ListBox1 と ListBox2) を含む進行状況フォーム(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

テストクラス:

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

このプログラムは、進行状況フォームを開き、2 つの長時間実行タスクを並行して実行し、2 つの長時間実行タスクの進行状況メッセージを表示し、両方の長時間実行タスクが終了したら進行状況フォームを閉じ、メッセージ ボックスに結果を表示することになっています。長時間実行されるタスク。

ここで説明できない動作について: このプログラムを実行すると、両方のリストボックスに「Run 1」が表示されるため、両方のタスクが実行を開始しましたが、最初のリストボックスのみがいっぱいになり、最初のリストボックスがいっぱいになったときにのみ (最初のタスクが完了した場合)、2 番目のリストボックスは引き続き入力されます。したがって、両方のタスクが開始されますが、2 番目のタスクは最初のタスクが終了するのを待ってから実行を続けます。
しかし、Task.WaitAll(testTask)メイン プロシージャで をコメント アウトすると、逆になります。両方のリストボックスに「Run 1」が表示され、2 番目のリストボックスがいっぱいになり、2 番目のリストボックスがいっぱいになったとき (2 番目のタスクが完了したとき) にのみ、最初のリストボックスが実行され続けます。

プログラムの動作がおかしいのはなぜですか? また、2 つのタスクを同時に実行するにはどうすればよいですか?

この小さな謎を解決するためにあなたの助けをありがとう:-)


更新これまでの唯一の回答は、私が使用
しているものに関係していると述べているため、メインプロシージャのコードから削除しました: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に、フォームのコードを次のように変更しました。

2 つのリストボックス (ListBox1 と ListBox2) を含む進行状況フォーム(Form1):

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うまくいきません。
誰か教えてください、何が悪いのですか?

またよろしくお願いいたします。

4

2 に答える 2

0

多くの検索と試行錯誤の後、私は解決策を思いつきました。きれいではありませんが、機能します。

Task.WaitAll() は、引き渡されたタスクが完了するのを待つだけでなく、呼び出されたタスクをブロックするように思えます。
したがって、メイン プロシージャのコードを次のように変更します。

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

...
Do While Not Task.WaitAll(testTask, 100)
  Application.DoEvents
Loop
...

トリックを行い、進行状況メッセージがリストボックスに表示されます。

于 2013-04-18T07:31:38.020 に答える