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

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

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

프로젝트 만들기

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

[!참고]

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

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

폼을 만들려면

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

  2. 응용 프로그램 Calculations의 이름을 지정하고 Form1.cs를 frmCalculations.cs로 이름을 바꿉니다.Visual Studio에서 Form1 코드 요소의 이름을 바꿀 것인지 묻는 메시지를 표시하면 를 클릭합니다.

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

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

  4. 이 컨트롤들의 속성을 다음과 같이 설정합니다.

    컨트롤

    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 int varAddTwo; 
    public int varFact1;
    public int varFact2;
    public int varLoopValue;
    public double varTotalCalculations = 0;
    

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

  1. 구성 요소와 폼 간에 값을 주고 받는 데 사용할 이벤트를 위한 대리자를 선언합니다.

    [!참고]

    네 개의 이벤트를 선언하지만 두 이벤트의 시그니처가 동일하므로 대리자를 세 개만 만들면 됩니다.

    앞 단계에서 입력한 변수 선언 바로 아래에 다음 코드를 입력합니다.

    // This delegate will be invoked with two of your events.
    public delegate void FactorialCompleteHandler(double Factorial, double TotalCalculations);
    public delegate void AddTwoCompleteHandler(int Result, double TotalCalculations);
    public delegate void LoopCompleteHandler(double TotalCalculations, int Counter);
    
  2. 구성 요소에서 응용 프로그램과 통신하는 데 사용할 이벤트를 선언합니다.이를 위해 앞 단계에서 입력한 코드 바로 아래에 다음 코드를 추가합니다.

    public event FactorialCompleteHandler FactorialComplete;
    public event FactorialCompleteHandler FactorialMinusOneComplete;
    public event AddTwoCompleteHandler AddTwoComplete;
    public event LoopCompleteHandler LoopComplete;
    
  3. 앞 단계에서 입력한 코드 바로 아래에 다음 코드를 입력합니다.

    // This method will calculate the value of a number minus 1 factorial
    // (varFact2-1!).
    public void FactorialMinusOne()
    {
       double varTotalAsOfNow = 0;
       double varResult = 1;
       // Performs a factorial calculation on varFact2 - 1.
       for (int varX = 1; varX <= varFact2 - 1; varX++)
       {
          varResult *= varX;
          // Increments varTotalCalculations and keeps track of the current 
          // total as of this instant.
          varTotalCalculations += 1;
          varTotalAsOfNow = varTotalCalculations;
       }
       // Signals that the method has completed, and communicates the 
       // result and a value of total calculations performed up to this 
       // point.
       FactorialMinusOneComplete(varResult, varTotalAsOfNow);
    }
    
    // This method will calculate the value of a number factorial.
    // (varFact1!)
    public void Factorial()
    {
       double varResult = 1;
       double varTotalAsOfNow = 0;
       for (int varX = 1; varX <= varFact1; varX++)
       {
          varResult *= varX;
          varTotalCalculations += 1;
          varTotalAsOfNow = varTotalCalculations;
       }
       FactorialComplete(varResult, varTotalAsOfNow);
    }
    
    // This method will add two to a number (varAddTwo+2).
    public void AddTwo()
    {
       double varTotalAsOfNow = 0;  
       int varResult = varAddTwo + 2;
       varTotalCalculations += 1;
       varTotalAsOfNow = varTotalCalculations;
       AddTwoComplete(varResult, varTotalAsOfNow);
    }
    
    // This method will run a loop with a nested loop varLoopValue times.
    public void RunALoop()
    {
       int varX;
       double varTotalAsOfNow = 0;
       for (varX = 1; varX <= varLoopValue; varX++)
       {
        // This nested loop is added solely for the purpose of slowing down
        // the program and creating a processor-intensive application.
          for (int varY = 1; varY <= 500; varY++)
          {
             varTotalCalculations += 1;
             varTotalAsOfNow = varTotalCalculations;
          }
       }
       LoopComplete(varTotalAsOfNow, varLoopValue);
    }
    

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

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

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

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

  2. public partial class frmCalculations 문을 찾아{ 바로 아래에 다음 코드를 입력합니다.

    Calculator Calculator1;
    
  3. 생성자를 찾은 다음} 바로 앞에 다음 줄을 추가합니다.

    // Creates a new instance of Calculator.
    Calculator1 = new Calculator();
    
  4. 디자이너에서 각 단추를 클릭하여 각 컨트롤의 Click 이벤트 처리기에 대한 코드 개요를 생성하고 처리기를 만드는 코드를 추가합니다.

    이 단계를 마치면 Click 이벤트 처리기는 다음과 같이 됩니다.

    // Passes the value typed in the txtValue to Calculator.varFact1.
    private void btnFactorial1_Click(object sender, System.EventArgs e)
    {
       Calculator1.varFact1 = int.Parse(txtValue.Text);
       // Disables the btnFactorial1 until this calculation is complete.
       btnFactorial1.Enabled = false;
       Calculator1.Factorial();
    }
    
    private void btnFactorial2_Click(object sender, System.EventArgs e)
    {
       Calculator1.varFact2 = int.Parse(txtValue.Text);
       btnFactorial2.Enabled = false;
       Calculator1.FactorialMinusOne();
    }
    private void btnAddTwo_Click(object sender, System.EventArgs e)
    {
       Calculator1.varAddTwo = int.Parse(txtValue.Text);
       btnAddTwo.Enabled = false;
       Calculator1.AddTwo();
    }
    private void btnRunLoops_Click(object sender, System.EventArgs e)
    {
       Calculator1.varLoopValue = int.Parse(txtValue.Text);
       btnRunLoops.Enabled = false;
       // Lets the user know that a loop is running
       lblRunLoops.Text = "Looping";
       Calculator1.RunALoop();
    }
    
  5. 앞 단계에서 추가한 코드 다음에 폼이 Calculator1로부터 수신할 이벤트를 처리하는 다음 코드를 입력합니다.

    private void FactorialHandler(double Value, double Calculations)
    // Displays the returned value in the appropriate label.
    {
       lblFactorial1.Text = Value.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 " + 
          Calculations.ToString();
    }
    
    private void FactorialMinusHandler(double Value, double Calculations)
    {
       lblFactorial2.Text = Value.ToString();
       btnFactorial2.Enabled = true;
       lblTotalCalculations.Text = "TotalCalculations are " + 
          Calculations.ToString();
    }
    
    private void AddTwoHandler(int Value, double Calculations)
    {
       lblAddTwo.Text = Value.ToString();
       btnAddTwo.Enabled = true;
       lblTotalCalculations.Text = "TotalCalculations are " +
          Calculations.ToString();
    }
    
    private void LoopDoneHandler(double Calculations, int Count)
    {
       btnRunLoops.Enabled = true;
       lblRunLoops.Text = Count.ToString();
       lblTotalCalculations.Text = "TotalCalculations are " +
          Calculations.ToString();
    }
    
  6. frmCalculations의 생성자에서 } 바로 앞에 다음 코드를 추가하여 폼이 Calculator1에서 받을 사용자 지정 이벤트를 처리합니다.

    Calculator1.FactorialComplete += new
       Calculator.FactorialCompleteHandler(this.FactorialHandler);
    Calculator1.FactorialMinusOneComplete += new
       Calculator.FactorialCompleteHandler(this.FactorialMinusHandler);
    Calculator1.AddTwoComplete += new
       Calculator.AddTwoCompleteHandler(this.AddTwoHandler);
    Calculator1.LoopComplete += new
       Calculator.LoopCompleteHandler(this.LoopDoneHandler);
    

응용 프로그램 테스트

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

프로젝트를 테스트하려면

  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.cs를 엽니다.

  2. 코드의 맨 위 부분에서 클래스 선언을 찾아 { 바로 아래에 다음 코드를 입력합니다.

    // Declares the variables you will use to hold your thread objects.
    public System.Threading.Thread FactorialThread; 
    public System.Threading.Thread FactorialMinusOneThread;  
    public System.Threading.Thread AddTwoThread; 
    public System.Threading.Thread LoopThread;
    
  3. 코드 아래쪽에 있는 클래스 선언 끝부분 바로 앞에 다음 메서드를 추가합니다.

    public void ChooseThreads(int threadNumber)
    {
    // Determines which thread to start based on the value it receives.
    switch(threadNumber)
       {
          case 1:
             // Sets the thread using the AddressOf the subroutine where
             // the thread will start.
             FactorialThread = new System.Threading.Thread(new
                System.Threading.ThreadStart(this.Factorial));
             // Starts the thread.
             FactorialThread.Start();
             break;
          case 2:
             FactorialMinusOneThread = new
                System.Threading.Thread(new
                   System.Threading.ThreadStart(this.FactorialMinusOne));
             FactorialMinusOneThread.Start();
             break;
          case 3:
             AddTwoThread = new System.Threading.Thread(new
                 System.Threading.ThreadStart(this.AddTwo));
             AddTwoThread.Start();
             break;
          case 4:
             LoopThread = new System.Threading.Thread(new
                System.Threading.ThreadStart(this.RunALoop));
             LoopThread.Start();
             break;
       }
    }
    

    Thread의 인스턴스를 만들 때는 ThreadStart 형식의 인수가 필요합니다.ThreadStart는 스레드가 시작되는 메서드의 주소를 가리키는 대리자입니다.ThreadStart는 매개 변수를 받거나 값을 전달할 수 없으므로 void 메서드만 나타낼 수 있습니다.방금 구현한 ChooseThreads 메서드는 그것을 호출하는 프로그램에서 받은 값을 사용하여 시작할 적절한 스레드를 결정합니다.

해당 코드를 frmCalculations에 추가하려면

  1. 코드 편집기에서 frmCalculations.cs 파일을 연 다음 private void btnFactorial1_Click을 찾습니다.

    1. 다음과 같이 Calculator1.Factorial1 메서드를 직접 호출하는 줄을 주석으로 처리합니다.

      // 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 void btnFactorial1_Click(object sender, System.EventArgs e)
    // Passes the value typed in the txtValue to Calculator.varFact1
    {
       Calculator1.varFact1 = int.Parse(txtValue.Text);
       // Disables the btnFactorial1 until this calculation is complete
       btnFactorial1.Enabled = false;
       // Calculator1.Factorial();
       Calculator1.ChooseThreads(1);
    }
    
    private void btnFactorial2_Click(object sender, System.EventArgs e)
    {
       Calculator1.varFact2 = int.Parse(txtValue.Text); 
       btnFactorial2.Enabled = false;         
       // Calculator1.FactorialMinusOne();
       Calculator1.ChooseThreads(2);
    }
    private void btnAddTwo_Click(object sender, System.EventArgs e)
    {
       Calculator1.varAddTwo = int.Parse(txtValue.Text);
       btnAddTwo.Enabled = false;
       // Calculator1.AddTwo();
       Calculator1.ChooseThreads(3);
    }
    
    private void btnRunLoops_Click(object sender, System.EventArgs e)
    {
       Calculator1.varLoopValue = int.Parse(txtValue.Text);
       btnRunLoops.Enabled = false;
       // Lets the user know that a loop is running
       lblRunLoops.Text = "Looping";
       // Calculator1.RunALoop();
       Calculator1.ChooseThreads(4);
    }
    

컨트롤 호출 마샬링

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

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

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

    public delegate void FHandler(double Value, double Calculations);
    public delegate void A2Handler(int Value, double Calculations);
    public delegate void LDHandler(double Calculations, int Count);
    

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

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

    public void FactHandler(double Value, double Calculations)
    {
    }
    public void Fact1Handler(double Value, double Calculations)
    {
    }
    public void Add2Handler(int Value, double Calculations)
    {
    }
    public void LDoneHandler(double Calculations, int Count)
    {
    }
    
  3. 편집 메뉴에서 잘라내기붙여넣기를 사용하여 FactorialHandler 메서드에서 모든 코드를 잘라내어 FactHandler에 붙여넣습니다.

  4. FactorialMinusHandler 및 Fact1Handler, AddTwoHandler 및 Add2Handler, LoopDoneHandler 및 LDoneHandler에 대해 이전 단계를 반복합니다.

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

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

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

    protected void FactorialHandler(double Value, double Calculations)
    {
       // 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. 
       this.BeginInvoke(new FHandler(FactHandler), new Object[]
          {Value, Calculations});
    }
    protected void FactorialMinusHandler(double Value, double Calculations)
    {
       this.BeginInvoke(new FHandler(Fact1Handler), new Object []
          {Value, Calculations});
    }
    
    protected void AddTwoHandler(int Value, double Calculations)
    {
       this.BeginInvoke(new A2Handler(Add2Handler), new Object[]
          {Value, Calculations});
    }
    
    protected void LoopDoneHandler(double Calculations, int Count)
    {
       this.BeginInvoke(new LDHandler(LDoneHandler), new Object[]
          {Calculations, Count});
    }
    

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

  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 C#에서 제공하는 lock 문(C# 참조)을 사용하면 스레드를 동기화하여 각 스레드에서 항상 올바른 결과를 반환하도록 할 수 있습니다.lock을 사용하기 위한 구문은 다음과 같습니다.

lock(AnObject)
{
   // Insert code that affects the object.
   // Insert more code that affects the object.
   // Insert more code that affects the object.
// Release the lock.
}

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

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

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

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

    varTotalCalculations += 1;
    varTotalAsOfNow = varTotalCalculations;
    

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

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

    lock(this)
    {
       varTotalCalculations += 1;
       varTotalAsOfNow = varTotalCalculations;
    }
    
  4. 작업한 내용을 저장하고 앞의 예제와 같이 테스트합니다.

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

참고 항목

작업

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

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

참조

BackgroundWorker

개념

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

기타 리소스

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

구성 요소 프로그래밍 연습

구성 요소에서 다중 스레딩