2013-04-11 19 views
1

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에) : 여기

내 코드입니다 :

012 3,516,
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"이 표시되므로 두 작업이 모두 실행되기 시작하지만 첫 번째 목록 상자 만 채워지고 첫 번째 listbox가 채워지면 (첫 번째 작업이 완료 됨) 두 번째 목록 상자가 계속 채워집니다. 따라서 두 작업 모두 시작되지만 두 번째 작업은 계속 실행하기 전에 첫 번째 작업이 완료 될 때까지 대기합니다.
그러나 내 주 절차에서 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 
0 내 주요 절차의 코드에서 그

진행 형태 (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 
을 다음과 같이

때문에 불법 크로스 스레드 호출의 InvalidOperationException을 피하기 위해, 나는 내 양식의 코드를 변경

디버깅 용으로 Debug.Prints를 몇 개 추가하고 InvokeRequired 부분을 추가했습니다.

이제는 두 작업이 병렬로 실행됩니다 ("out List"메시지로 Debug.Print 호출로 인해 알 수 있음)하지만 진행률 메시지가 목록 상자에 표시되지 않고 해당 코드가 실행되지 않습니다 (Debug.Print은 " in List "메시지가 디버그 창에 표시되지 않습니다.

InvokeRequired 부분을 수행하는 데 올바른 방법으로 검색해 보았습니다. 올바른 방식으로 작동하지만 작동하지 않습니다.
누군가 나에게 말해 줄 수 있습니까? 무엇이 잘못 되었나요?

도움에 다시 한 번 감사드립니다.

답변

2

현재 동기화 컨텍스트에서 작업을 실행하도록 시스템에 알리는 것입니다.

TaskScheduler.FromCurrentSynchronizationContext 

이 인스턴스에서 해당 동기화 컨텍스트는 UI 스레드입니다. 따라서 작업은 UI 스레드에서 실행될 때까지 큐에 대기하게됩니다. WaitAll() 호출을 입력 할 때만 사용할 수있게됩니다 (WaitAll 규칙에 따라 현재 스레드가 하나 이상의 작업을 실행하는 데 적합 할 경우 현재 실행중인 스레드가 해당 작업 중 하나 이상을 직접 실행할 수 있습니다.)

작업이 실행되도록 요청하십시오. 다른 동기화 컨텍스트 (또는 스레드 풀)하지만 다른 문제가 발생합니다. 스레드가 발생하는 이벤트에서 UI 요소와 상호 작용합니다.

코드는 별도의 스레드에서 실행되는 BackgroundWorker 클래스를 조사해야하지만 UI/Foreground 스레드로 진행 상황을보고하기 위해 특별히 작성되었습니다.

+0

태스크를 생성 할 때'TaskScheduler.FromCurrentSynchronizationContext'를 넘겨주지 않고 대신 폼의 코드에서'InvokeRequired'를 사용하면 프로그램은 아무런 진전도 보이지 않고 폼은 열리고 대기 만 보여줍니다 커서 그게 다야. 이것이 UI 스레드를 차단하지 않고 작업을 실행하는 방법에 대한 첫 번째 질문의 주제였습니다. – Nostromo

+0

이미'BackgroundWorker'를 시도해 보았지만 실행 중이지만, 1. 'BackgroundWorker'를 사용하고 싶지 않습니다. 구성 요소이고 제대로 작동하기 위해 양식이 필요하기 때문에이 양식을 다시 사용할 수 없기 때문에 코드 및 이벤트 처리는 양식에서 발생합니다. 2. 멀티 스레딩에 대해 배우고 싶습니다. 따라서 그것이 작동하는 방식이 아니더라도 그것이 왜 작동 하는지를 이해하고 싶습니다. 두 개의'Task's를 병렬로 실행시키고 ** 진행 상황을 보여주기 위해해야 ​​할 일이 있습니까? – Nostromo

+0

@Nostromo - 만약 당신이'InvokeRequired' (그리고'Invoking')를 사용하고 있지만 당신의 UI 스레드가'WaitAll()'호출기 안에 앉아 있다면, 여전히 작동하지 않을 것입니다 - UI 스레드 *는 할 수 없습니다 * WaitAll() 호출 - Invoke 요청을 서비스 할 수 있어야한다. –

0

많은 검색과 시도 끝에 해결책을 찾았습니다. 그것은 꽤 아니지만 그것은 작동합니다.

Task.WaitAll()은 넘겨주는 작업이 끝날 때까지 기다릴뿐만 아니라 블록을 호출하는 것으로 보입니다.
그래서,

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

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

에서 내 주요 절차의 코드를 변경하면 트릭을 수행하고 내 진행 메시지는리스트 박스에 표시하세요.

관련 문제