연습: Visual Basic으로 간단한 다중 스레드 구성 요소 만들기

BackgroundWorkerSystem.Threading 네임스페이스를 대체하고 여기에 다른 기능을 추가하여 새로 도입된 구성 요소이지만 이전 버전과의 호환성 및 이후 사용 가능성을 고려하여 System.Threading 네임스페이스를 계속 유지하도록 선택할 수 있습니다.자세한 내용은 BackgroundWorker 구성 요소 개요를 참조하십시오.

여러 작업을 동시에 수행할 수 있는 응용 프로그램을 만들 수 있습니다.다중 스레딩 또는 자유 스레딩이라는 이러한 강력한 기능을 이용하면 프로세서를 많이 사용하고 사용자 입력을 요구하는 구성 요소를 디자인할 수 있습니다.다중 스레딩을 활용할 수 있는 구성 요소의 예로 급료 정보를 계산하는 구성 요소가 있습니다.이 구성 요소에서는 프로세서를 많이 사용하는 급료 계산을 한 스레드에서 처리하는 동안 다른 스레드에서는 사용자가 데이터베이스에 입력한 데이터를 처리할 수 있습니다.이러한 프로세스가 별개의 스레드에서 실행되도록 하면 사용자는 컴퓨터에서 계산을 마칠 때까지 기다릴 필요 없이 추가로 데이터를 입력할 수 있습니다.이 연습에서는 복잡한 여러 계산을 동시에 수행하는 간단한 다중 스레드 구성 요소를 만듭니다.

프로젝트 만들기

이 연습에서 만드는 응용 프로그램은 단일 폼 및 구성 요소로 이루어집니다.폼에서는 계산을 시작하도록 구성 요소에 값과 신호를 입력합니다.그런 다음 값을 받아 레이블 컨트롤에 표시합니다.구성 요소에서는 프로세서를 많이 사용하는 계산을 수행하고 계산이 완료되면 폼에 신호를 보냅니다.사용자 인터페이스를 통해 받은 값을 저장할 공용 변수를 구성 요소에서 만들고이 변수의 값을 바탕으로 계산을 수행하는 메서드도 구현합니다.

[!참고]

값을 계산하는 방법으로 대개 함수를 사용하지만 스레드 간에 인수를 전달하거나 값을 반환할 수 없는 문제가 있습니다.스레드에 값을 제공하고 스레드에서 값을 받는 간단한 방법이 여러 가지 있습니다.이 연습에서는 공용 변수를 업데이트하여 값을 사용자 인터페이스로 반환하고, 스레드 실행이 완료되었을 때 이벤트를 사용하여 주 프로그램에 알립니다.

표시되는 대화 상자와 메뉴 명령은 활성 설정이나 버전에 따라 도움말에서 설명하는 것과 다를 수 있습니다.설정을 변경하려면 도구 메뉴에서 설정 가져오기 및 내보내기를 선택합니다.자세한 내용은 Visual Studio 설정을 참조하십시오.

폼을 만들려면

  1. Windows 응용 프로그램 프로젝트를 새로 만듭니다.

  2. 응용 프로그램 Calculations의 이름을 지정하고 Form1.vb를 frmCalculations.vb로 바꿉니다.

  3. Visual Studio에서 Form1 코드 요소의 이름을 바꿀 것인지 묻는 메시지를 표시하면 를 클릭합니다.

    이 폼은 응용 프로그램의 기본 사용자 인터페이스가 됩니다.

  4. Label 컨트롤 다섯 개, Button 컨트롤 네 개 및 TextBox 컨트롤 한 개를 폼에 추가합니다.

    컨트롤

    Name

    Text

    Label1

    lblFactorial1

    (비어 있음)

    Label2

    lblFactorial2

    (비어 있음)

    Label3

    lblAddTwo

    (비어 있음)

    Label4

    lblRunLoops

    (비어 있음)

    Label5

    lblTotalCalculations

    (비어 있음)

    Button1

    btnFactorial1

    Factorial

    Button2

    btnFactorial2

    Factorial - 1

    Button3

    btnAddTwo

    Add Two

    Button4

    btnRunLoops

    Run a Loop

    TextBox1

    txtValue

    (비어 있음)

Calculator 구성 요소를 만들려면

  1. 프로젝트 메뉴에서 구성 요소 추가를 선택합니다.

  2. 이 구성 요소 Calculator의 이름을 지정합니다.

공용 변수를 Calculator 구성 요소에 추가하려면

  1. Calculator에 대한 코드 편집기를 엽니다.

  2. frmCalculations에서 각 스레드로 값을 전달하는 데 사용할 공용 변수를 만드는 문을 추가합니다.

    varTotalCalculations 변수에는 구성 요소에서 수행한 총 계산 횟수에 대한 누계가 유지되며 나머지 변수들은 폼에서 값을 받습니다.

    Public varAddTwo As Integer
    Public varFact1 As Integer
    Public varFact2 As Integer
    Public varLoopValue As Integer
    Public varTotalCalculations As Double = 0
    

메서드와 이벤트를 Calculator 구성 요소에 추가하려면

  1. 구성 요소에서 값을 폼으로 전달하는 데 사용할 이벤트를 선언합니다.앞 단계에서 입력한 변수 선언 바로 아래에 다음 코드를 입력합니다.

    Public Event FactorialComplete(ByVal Factorial As Double, ByVal _
       TotalCalculations As Double)
    Public Event FactorialMinusComplete(ByVal Factorial As Double, ByVal _
       TotalCalculations As Double)
    Public Event AddTwoComplete(ByVal Result As Integer, ByVal _
       TotalCalculations As Double)
    Public Event LoopComplete(ByVal TotalCalculations As Double, ByVal _
       Counter As Integer)
    
  2. 1단계에서 입력한 변수 선언 바로 아래에 다음 코드를 입력합니다.

    ' This sub will calculate the value of a number minus 1 factorial 
    ' (varFact2-1!).
    Public Sub FactorialMinusOne()
       Dim varX As Integer = 1
       Dim varTotalAsOfNow As Double
       Dim varResult As Double = 1
       ' Performs a factorial calculation on varFact2 - 1.
       For varX = 1 to varFact2 - 1
          varResult *= varX
          ' Increments varTotalCalculations and keeps track of the current
          ' total as of this instant.
          varTotalCalculations += 1
          varTotalAsOfNow = varTotalCalculations
       Next varX
       ' Signals that the method has completed, and communicates the 
       ' result and a value of total calculations performed up to this 
       ' point
       RaiseEvent FactorialMinusComplete(varResult, varTotalAsOfNow)
    End Sub
    
    ' This sub will calculate the value of a number factorial (varFact1!).
    Public Sub Factorial()
       Dim varX As Integer = 1
       Dim varResult As Double = 1
       Dim varTotalAsOfNow As Double = 0
       For varX = 1 to varFact1
           varResult *= varX
           varTotalCalculations += 1
           varTotalAsOfNow = varTotalCalculations
       Next varX
       RaiseEvent FactorialComplete(varResult, varTotalAsOfNow)
    End Sub
    
    ' This sub will add two to a number (varAddTwo + 2).
    Public Sub AddTwo()
       Dim varResult As Integer
       Dim varTotalAsOfNow As Double
       varResult = varAddTwo + 2
       varTotalCalculations += 1
       varTotalAsOfNow = varTotalCalculations
       RaiseEvent AddTwoComplete(varResult, varTotalAsOfNow)
    End Sub
    
    ' This method will run a loop with a nested loop varLoopValue times.
    Public Sub RunALoop()
       Dim varX As Integer
       Dim varY As Integer
       Dim varTotalAsOfNow As Double
       For varX = 1 To varLoopValue
          ' This nested loop is added solely for the purpose of slowing
          ' down the program and creating a processor-intensive
          ' application.
          For varY = 1 To 500
             varTotalCalculations += 1
             varTotalAsOfNow = varTotalCalculations
          Next
       Next
       RaiseEvent LoopComplete(varTotalAsOfNow, varX - 1)
    End Sub
    

사용자 입력을 구성 요소에 전송

다음 단계에서는 사용자의 입력을 받는 코드와 Calculator 구성 요소 간에 값을 주고 받기 위한 코드를 frmCalculations에 추가합니다.

프런트 엔드 기능을 frmCalculations에 구현하려면

  1. 빌드 메뉴에서 솔루션 빌드를 선택합니다.

  2. Windows Forms 디자이너에서 frmCalculations를 엽니다.

  3. 도구 상자에서 Calculator 구성 요소 탭을 찾습니다.Calculator 구성 요소를 디자인 화면으로 끕니다.

  4. 속성 창에서 이벤트 단추를 클릭합니다.

  5. 네 개의 이벤트를 각각 두 번 클릭하여 frmCalculations에 이벤트 처리기를 만듭니다.각 이벤트 처리기가 만들어지면 다시 디자이너로 돌아갑니다.

  6. 폼이 Calculator1에서 받는 이벤트를 처리하도록 다음 코드를 삽입합니다.

    Private Sub Calculator1_AddTwoComplete(ByVal Result As System.Int32, ByVal TotalCalculations As System.Double) Handles Calculator1.AddTwoComplete
        lblAddTwo.Text = Result.ToString
        btnAddTwo.Enabled = True
        lblTotalCalculations.Text = "TotalCalculations are " & _
            TotalCalculations.ToString
    End Sub
    
    Private Sub Calculator1_FactorialComplete(ByVal Factorial As System.Double, ByVal TotalCalculations As System.Double) Handles Calculator1.FactorialComplete
        ' Displays the returned value in the appropriate label.
        lblFactorial1.Text = Factorial.ToString
        ' Re-enables the button so it can be used again.
        btnFactorial1.Enabled = True
        ' Updates the label that displays the total calculations performed
        lblTotalCalculations.Text = "TotalCalculations are " & _
           TotalCalculations.ToString
    End Sub
    
    Private Sub Calculator1_FactorialMinusComplete(ByVal Factorial As System.Double, ByVal TotalCalculations As System.Double) Handles Calculator1.FactorialMinusComplete
        lblFactorial2.Text = Factorial.ToString
        btnFactorial2.Enabled = True
        lblTotalCalculations.Text = "TotalCalculations are " & _
            TotalCalculations.ToString
    End Sub
    
    Private Sub Calculator1_LoopComplete(ByVal TotalCalculations As System.Double, ByVal Counter As System.Int32) Handles Calculator1.LoopComplete
        btnRunLoops.Enabled = True
        lblRunLoops.Text = Counter.ToString
        lblTotalCalculations.Text = "TotalCalculations are " & _
           TotalCalculations.ToString
    End Sub
    
  7. 코드 편집기의 아래쪽에서 End Class 문을 찾아바로 위에 다음 코드를 추가하여 단추 클릭을 처리합니다.

    Private Sub btnFactorial1_Click(ByVal sender As Object, ByVal e As _
       System.EventArgs) Handles btnFactorial1.Click
       ' Passes the value typed in the txtValue to Calculator.varFact1.
       Calculator1.varFact1 = CInt(txtValue.Text)
       ' Disables the btnFactorial1 until this calculation is complete.
       btnFactorial1.Enabled = False
       Calculator1.Factorial()
    End Sub
    
    Private Sub btnFactorial2_Click(ByVal sender As Object, ByVal e _
       As System.EventArgs) Handles btnFactorial2.Click
       Calculator1.varFact2 = CInt(txtValue.Text)
       btnFactorial2.Enabled = False
       Calculator1.FactorialMinusOne()
    End Sub
    
    Private Sub btnAddTwo_Click(ByVal sender As Object, ByVal e As _
       System.EventArgs) Handles btnAddTwo.Click
       Calculator1.varAddTwo = CInt(txtValue.Text)
       btnAddTwo.Enabled = False
       Calculator1.AddTwo()
    End Sub
    
    Private Sub btnRunLoops_Click(ByVal sender As Object, ByVal e As _
       System.EventArgs) Handles btnRunLoops.Click
       Calculator1.varLoopValue = CInt(txtValue.Text)
       btnRunLoops.Enabled = False
       ' Lets the user know that a loop is running.
       lblRunLoops.Text = "Looping"
       Calculator1.RunALoop()
    End Sub
    

응용 프로그램 테스트

지금까지 여러 가지 복잡한 계산을 수행할 수 있는 구성 요소와 폼을 통합하는 프로젝트를 만들었습니다.다중 스레드 기능은 아직 구현하지 않았지만 작업을 더 진행하기 전에 기능을 확인하기 위해 프로젝트를 테스트합니다.

프로젝트를 테스트하려면

  1. 디버그 메뉴에서 디버깅 시작을 선택합니다.응용 프로그램이 시작되고 frmCalculations가 나타납니다.

  2. 텍스트 상자에 4를 입력한 다음 Add Two 단추를 클릭합니다.

    숫자 "6"이 단추 아래의 레이블에 표시되고 "Total Calculations are 1"이 lblTotalCalculations에 나타납니다.

  3. 이제 Factorial - 1 단추를 클릭합니다.

    숫자 "6"이 단추 아래에 표시되고 lblTotalCalculations에는 "Total Calculations are 4"가 표시됩니다.

  4. 텍스트 상자의 값을 20으로 변경한 다음 Factorial 단추를 클릭합니다.

    숫자 "2.43290200817664E+18"이 단추 아래에 표시되고 lblTotalCalculations에는 "Total Calculations are 24"가 나타납니다.

  5. 텍스트 상자의 값을 50000으로 바꾼 다음 Run a Loop 단추를 클릭합니다.

    이 때 약간의 시간이 지난 후 이 단추가 다시 사용할 수 있는 상태가 됩니다.이 단추 아래의 레이블에 "50000"이 표시되고 총 계산 횟수는 "25000024"로 나타납니다.

  6. 텍스트 상자의 값을 5000000으로 변경한 후 Run A Loop 단추를 클릭한 다음 바로 Add Two 단추를 클릭합니다.Add Two를 다시 클릭합니다.

    단추가 응답하지 않으며 루프가 완료될 때까지 폼에 있는 모든 컨트롤도 응답하지 않습니다.

    프로그램에서 스레드를 하나만 실행하는 경우에는 위 예제와 같이 프로세서를 많이 사용하는 계산 작업을 수행할 때 계산이 종료될 때까지 프로그램이 정체될 수 있습니다.다음 단원에서는 한 번에 여러 스레드를 실행할 수 있도록 응용 프로그램에 다중 스레딩 기능을 추가합니다.

다중 스레딩 기능 추가

앞 예제를 통해 스레드를 하나만 실행하는 응용 프로그램에서 겪는 제한 사항을 살펴보았습니다.다음 단원에서는 Thread 클래스를 사용하여 여러 실행 스레드를 구성 요소에 추가합니다.

Threads 서브루틴을 추가하려면

  1. 코드 편집기에서 Calculator.vb를 엽니다.코드의 맨 위 부분에서 Public Class Calculator 줄을 찾아바로 아래에 다음 코드를 입력합니다.

    ' Declares the variables you will use to hold your thread objects.
    Public FactorialThread As System.Threading.Thread
    Public FactorialMinusOneThread As System.Threading.Thread
    Public AddTwoThread As System.Threading.Thread
    Public LoopThread As System.Threading.Thread
    
  2. 코드 아래쪽에 있는 End Class 문 바로 앞에 다음 메서드를 추가합니다.

    Public Sub ChooseThreads(ByVal threadNumber As Integer)
    ' Determines which thread to start based on the value it receives.
       Select Case threadNumber
          Case 1
             ' Sets the thread using the AddressOf the subroutine where
             ' the thread will start.
             FactorialThread = New System.Threading.Thread(AddressOf _
                Factorial)
             ' Starts the thread.
             FactorialThread.Start()
          Case 2
             FactorialMinusOneThread = New _
                System.Threading.Thread(AddressOf FactorialMinusOne)
             FactorialMinusOneThread.Start()
          Case 3
             AddTwoThread = New System.Threading.Thread(AddressOf AddTwo)
             AddTwoThread.Start()
          Case 4
             LoopThread = New System.Threading.Thread(AddressOf RunALoop)
             LoopThread.Start()
       End Select
    End Sub
    

    Thread 개체의 인스턴스를 만들 때는 ThreadStart 개체 형식의 인수가 필요합니다.ThreadStart 개체는 스레드가 시작되는 서브루틴의 주소를 가리키는 대리자입니다.ThreadStart 개체는 매개 변수를 받거나 값을 전달할 수 없으므로 함수를 나타낼 수 없습니다.AddressOf 연산자(Visual Basic)에서는 ThreadStart 개체 역할을 하는 대리자를 반환합니다.방금 구현한 ChooseThreads sub는 그것을 호출하는 프로그램에서 받은 값을 사용하여 시작할 적절한 스레드를 결정합니다.

스레드 시작 코드를 frmCalculations에 추가하려면

  1. 코드 편집기에서 frmCalculations.vb 파일을 열고Sub btnFactorial1_Click을 찾습니다.

    1. 호출 하는 줄을 주석으로 처리 된 Calculator1.Factorial메서드로 직접 표시:

      ' Calculator1.Factorial
      
    2. Calculator1.ChooseThreads 메서드에 다음 줄을 추가합니다.

      ' Passes the value 1 to Calculator1, thus directing it to start the ' correct thread.
      Calculator1.ChooseThreads(1)
      
  2. 다른 button_click 서브루틴도 이와 비슷하게 수정합니다.

    [!참고]

    threads 인수에 적합한 값을 포함해야 합니다.

    작업을 마치면 코드가 다음과 같이 됩니다.

    Private Sub btnFactorial1_Click(ByVal sender As Object, ByVal e As _
       System.EventArgs) Handles btnFactorial1.Click
       ' Passes the value typed in the txtValue to Calculator.varFact1.
       Calculator1.varFact1 = CInt(txtValue.Text)
       ' Disables the btnFactorial1 until this calculation is complete.
       btnFactorial1.Enabled = False
       ' Calculator1.Factorial()
       ' Passes the value 1 to Calculator1, thus directing it to start the
       ' Correct thread.
       Calculator1.ChooseThreads(1)
    End Sub
    
    Private Sub btnFactorial2_Click(ByVal sender As Object, ByVal e As _
       System.EventArgs) Handles btnFactorial2.Click
       Calculator1.varFact2 = CInt(txtValue.Text)
       btnFactorial2.Enabled = False
       ' Calculator1.FactorialMinusOne()
       Calculator1.ChooseThreads(2)
    End Sub
    
    Private Sub btnAddTwo_Click(ByVal sender As Object, ByVal e As _
       System.EventArgs) Handles btnAddTwo.Click
       Calculator1.varAddTwo = CInt(txtValue.Text)
       btnAddTwo.Enabled = False
       ' Calculator1.AddTwo()
       Calculator1.ChooseThreads(3)
    End Sub
    
    Private Sub btnRunLoops_Click(ByVal sender As Object, ByVal e As _
       System.EventArgs) Handles btnRunLoops.Click
       Calculator1.varLoopValue = CInt(txtValue.Text)
       btnRunLoops.Enabled = False
       ' Lets the user know that a loop is running.
       lblRunLoops.Text = "Looping"
       ' Calculator1.RunALoop()
       Calculator1.ChooseThreads(4)
    End Sub
    

컨트롤 호출 마샬링

이제 폼의 디스플레이를 간편하게 업데이트하도록 만듭니다.컨트롤은 항상 실행의 주 스레드에서 소유하므로 하위 스레드에서 컨트롤을 호출하려면 호출을 마샬링해야 합니다.마샬링은 스레드 경계를 넘어 호출을 이동하는 작업이며 리소스가 많이 소모됩니다.마샬링의 발생을 최소화하고 스레드로부터 안전한 방식으로 호출을 처리하기 위해 여기서는 BeginInvoke를 사용하여 실행의 주 스레드에 대해 메서드를 호출함으로써 스레드 경계를 넘는 마샬링의 발생을 최소화합니다.이런 종류의 호출은 컨트롤을 조작하는 메서드를 호출할 때 필요합니다.자세한 내용은 방법: 스레드에서 컨트롤 조작을 참조하십시오.

컨트롤 호출 프로시저를 만들려면

  1. frmCalculations에 대한 코드 편집기를 엽니다.선언 부분에 다음 코드를 추가합니다.

    Public Delegate Sub FHandler(ByVal Value As Double, ByVal _
       Calculations As Double)
    Public Delegate Sub A2Handler(ByVal Value As Integer, ByVal _
       Calculations As Double)
    Public Delegate Sub LDhandler(ByVal Calculations As Double, ByVal _
       Count As Integer)
    

    InvokeBeginInvoke에는 적절한 메서드에 대한 대리자가 인수로 필요합니다.이 줄에서는 적절한 메서드를 호출하기 위해 BeginInvoke에서 사용할 대리자 시그니처를 선언합니다.

  2. 아래와 같은 빈 메서드를 코드에 추가합니다.

    Public Sub FactHandler(ByVal Factorial As Double, ByVal TotalCalculations As _
       Double)
    End Sub
    Public Sub Fact1Handler(ByVal Factorial As Double, ByVal TotalCalculations As _
       Double)
    End Sub
    Public Sub Add2Handler(ByVal Result As Integer, ByVal TotalCalculations As _
       Double)
    End Sub
    Public Sub LDoneHandler(ByVal TotalCalculations As Double, ByVal Counter As _
       Integer)
    End Sub
    
  3. 편집 메뉴에서 잘라내기붙여넣기를 사용하여 Sub Calculator1_FactorialComplete 에서 모든 코드를 잘라내어 FactHandler에 붙여넣습니다.

  4. Calculator1_FactorialMinusComplete 및 Fact1Handler, Calculator1_AddTwoComplete 및 Add2Handler, Calculator1_LoopComplete 및 LDoneHandler에 대해 이전 단계를 반복합니다.

    이 작업을 마치면 Calculator1_FactorialComplete, Calculator1_FactorialMinusComplete, Calculator1_AddTwoComplete 및 Calculator1_LoopComplete에 코드가 남아 있지 않고 포함되어 있던 모든 코드가 적절한 새 메서드로 이동되어 있어야 합니다.

  5. BeginInvoke 메서드를 호출하여 비동기적으로 메서드를 호출합니다.BeginInvoke를 폼(me) 또는 폼에 있는 컨트롤에서 호출할 수 있습니다.

    Private Sub Calculator1_FactorialComplete(ByVal Factorial As System.Double, ByVal TotalCalculations As System.Double) Handles Calculator1.FactorialComplete
       ' BeginInvoke causes asynchronous execution to begin at the address
       ' specified by the delegate. Simply put, it transfers execution of 
       ' this method back to the main thread. Any parameters required by 
       ' the method contained at the delegate are wrapped in an object and 
       ' passed. 
       Me.BeginInvoke(New FHandler(AddressOf FactHandler), New Object() _
          {Factorial, TotalCalculations })
    End Sub
    
    Private Sub Calculator1_FactorialMinusComplete(ByVal Factorial As System.Double, ByVal TotalCalculations As System.Double) Handles Calculator1.FactorialMinusComplete
       Me.BeginInvoke(New FHandler(AddressOf Fact1Handler), New Object() _
          { Factorial, TotalCalculations })
    End Sub
    
    Private Sub Calculator1_AddTwoComplete(ByVal Result As System.Int32, ByVal TotalCalculations As System.Double) Handles Calculator1.AddTwoComplete
       Me.BeginInvoke(New A2Handler(AddressOf Add2Handler), New Object() _
          { Result, TotalCalculations })
    End Sub
    
    Private Sub Calculator1_LoopComplete(ByVal TotalCalculations As System.Double, ByVal Counter As System.Int32) Handles Calculator1.LoopComplete
       Me.BeginInvoke(New LDHandler(AddressOf Ldonehandler), New Object() _
          { TotalCalculations, Counter })
    End Sub
    

    이벤트 처리기가 단순히 그 다음 메서드를 호출하는 것처럼 보이지만,실제로는 작업의 주 스레드에 대해 메서드가 호출되도록 합니다.이 방법을 사용하면 스레드 경계를 넘는 호출을 줄임으로써 다중 스레드 응용 프로그램이 잠길 염려 없이 효율적으로 실행될 수 있습니다.다중 스레드 환경에서 컨트롤에 대해 작업하는 방법은 방법: 스레드에서 컨트롤 조작을 참조하십시오.

  6. 작업한 내용을 저장합니다.

  7. 디버그 메뉴에서 디버깅 시작을 선택하여 솔루션을 테스트합니다.

    1. 텍스트 상자에 10000000을 입력하고 Run A Loop를 클릭합니다.

      이 단추 아래의 레이블에 "Looping"이 표시됩니다.이 루프는 실행하는 데 시간이 많이 걸립니다.루프가 너무 빨리 종료되면 숫자를 더 크게 늘립니다.

    2. 사용 가능한 세 단추 모두를 연이어 빠르게 클릭합니다.모든 단추가 입력에 응답하는 것을 알 수 있습니다.Add Two 아래의 레이블에 가장 먼저 결과가 표시되고,Factorial 단추 아래의 레이블에는 결과가 나중에 표시됩니다.10,000,000 계승에 의해 반환되는 숫자가 너무 커 배정밀도 변수에 저장할 수 없기 때문에 이 결과는 무한대가 됩니다.잠시 후에 마지막으로 Run A Loop 단추 아래에 반환 결과가 표시됩니다.

      방금 보았듯이 각각 별개인 네 가지 계산 작업이 네 개의 개별 스레드에서 동시에 수행되었습니다.사용자 인터페이스는 여전히 입력에 응답하는 상태로 있고 각 스레드가 완료된 후 결과가 반환됩니다.

스레드 조정

다중 스레드 응용 프로그램에 익숙한 사용자라면 위에서 입력한 코드에 결함이 있다는 것을 알 것입니다.Calculator에서 계산을 수행하는 각 서브루틴에 다음과 같은 코드가 있습니다.

varTotalCalculations += 1
varTotalAsOfNow = varTotalCalculations

이 두 줄의 코드는 공용 변수 varTotalCalculations의 값을 증가시키고 지역 변수 varTotalAsOfNow를 이 값으로 설정합니다.그런 다음 이 값은 frmCalculations로 반환되어 레이블 컨트롤에 표시됩니다.그러면 반환되는 값이 올바른 것일까요?단일 스레드만 실행할 때는 올바른 값이 반환되는 것이 분명하지만,여러 스레드를 실행할 경우에는 반환 값이 올바르다고 단정할 수 없습니다.각 스레드에서 varTotalCalculations 변수의 값을 증가시킬 수 있습니다.한 스레드에서 이 변수의 값을 증가시킨 후 값을 varTotalAsOfNow로 복사하기 전에 다른 스레드에서 이 변수를 증가시켜 값이 바뀌는 경우도 발생할 수 있습니다.이런 경우 각 스레드에서 실제로 정확하지 않은 결과를 보고할 수도 있습니다.Visual Basic에서 제공하는 SyncLock 문을 사용하면 스레드를 동기화하여 각 스레드에서 항상 올바른 결과를 반환하도록 할 수 있습니다.SyncLock을 사용하기 위한 구문은 다음과 같습니다.

SyncLock AnObject
   Insert code that affects the object
   Insert some more
   Insert even more
' Release the lock
End SyncLock

SyncLock 블록을 입력하면 지정한 스레드가 해당 개체에서 단독으로 잠길 때까지 지정한 식의 실행이 차단됩니다.위의 예제에서처럼 실행은 AnObject에서 차단됩니다.SyncLock은 값이 아닌 참조를 반환하는 개체와 함께 사용해야 합니다.그러면 다른 스레드의 방해를 받지 않고 식이 블록으로 수행됩니다.이렇게 한 단위로 실행되는 문의 집합을 원자 단위라고 합니다.End SyncLock이 있는 곳까지 수행되고 나면 식이 해제되고 스레드가 정상적으로 진행될 수 있습니다.

SyncLock 문을 응용 프로그램에 추가하려면

  1. 코드 편집기에서 Calculator.vb를 엽니다.

  2. 다음과 같은 코드를 모두 찾습니다.

    varTotalCalculations += 1
    varTotalAsOfNow = varTotalCalculations
    

    이 코드는 계산을 수행하는 각 메서드에 한 번씩 모두 네 번 나옵니다.

  3. 이 코드를 다음과 같이 수정합니다.

    SyncLock Me
       varTotalCalculations += 1
       varTotalAsOfNow = varTotalCalculations
    End SyncLock
    
  4. 작업한 내용을 저장하고 앞의 예제와 같이 테스트합니다.

    프로그램 수행이 약간 느려진 것을 느낄 수도 있습니다.이는 구성 요소에 대한 단독 잠금을 얻을 때 스레드의 실행이 중지되기 때문입니다.이 방법을 사용하면 정확성은 향상되지만 다중 스레드에서 얻을 수 있는 성능 이점이 줄어듭니다.따라서 스레드를 잠글 필요가 있는지 주의 깊게 고려하여 꼭 필요할 때만 사용하는 것이 좋습니다.

참고 항목

작업

방법: 다중 스레드 실행 조정

연습: Visual C#으로 간단한 다중 스레드 구성 요소 만들기

참조

BackgroundWorker

개념

이벤트 기반 비동기 패턴 개요

기타 리소스

구성 요소를 사용한 프로그래밍

구성 요소 프로그래밍 연습

구성 요소에서 다중 스레딩