Programming/C#

C# Invoke와 BeginInvoke의 차이점 (목적 / 정의 / 사용 방법 / 예제)

JeongKyun 2022. 2. 18.
반응형

서론

이번 글은 Winform Application에서 스레드 환경을 구성할 때 사용하는 기술인 Invoke와 BeginInvoke의 정의와 차이점에 대해 알아볼 것이다. 우선 해당 글을 작성하기 앞서 스레드를 이용하여 UI를 조작할 때 발생하는 크로스 스레드의 글과 관련 있으니 이전에 작성한 크로스 스레드 해결 방법의 글을 참고하고 해당 글을 읽으면 더 이해하는데 도움이 될 것 이라 생각한다.

 


 

Invoke & BeginInvoke 목적

UI 컨트롤박스를 생성하게 되면 내부 스레드가 자동으로 생성되는데 별도의 스레드를 생성하여 해당 컨트롤 박스에 접근하려 하면 서로 다른 스레드가 하나의 컨트롤 박스 객체에 접근을 하게 되는데, 이 때 교착상태(크로스 스레드)가 발생하여 Invoke 또는 BeginInvoke를 사용하여 해당 크로스 스레드 문제를 방지하는데 목적이 있다.

 


 

[Invoke & BeginInvoke 알아보기]

Invoke란?

1. Control.Invoke ? 
> 컨트롤의 내부 핸들이 있는 스레드에서 지정된 대리자를 실행하는 방법
: UI 컨트롤 스레드에서 실행되지만 호출 스레드가 실행되기 앞서 기존 스레드 완료를 기다리고 호출된다.

2. Delegate.Invoke ?
> 동일한 스레드에서 사용할 대리자를 동기적으로 실행하는 방법

** Invoke 정리
> 컨트롤의 본인 스레드가 아닌 다른 스레드를 이용하여 해당 컨트롤 객체를 동기식으로 실행하는 방법이다.

 

Invoke 메서드 구조

public Object Invoke(
        Delegate method, --대리자 메서드
        params Object[] args -- 대리자 파라미터 (생략 가능)
)

 

Invoke 사용 예제 (Control.Invoke)

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;

   public class MyFormControl : Form
   {
      public delegate void AddListItem(String myString);
      public AddListItem myDelegate;
      private Button myButton;
      private Thread myThread;
      private ListBox myListBox;
      public MyFormControl()
      {
         myButton = new Button();
         myListBox = new ListBox();
         myButton.Location = new Point(72, 160);
         myButton.Size = new Size(152, 32);
         myButton.TabIndex = 1;
         myButton.Text = "Add items in list box";
         myButton.Click += new EventHandler(Button_Click);
         myListBox.Location = new Point(48, 32);
         myListBox.Name = "myListBox";
         myListBox.Size = new Size(200, 95);
         myListBox.TabIndex = 2;
         ClientSize = new Size(292, 273);
         Controls.AddRange(new Control[] {myListBox,myButton});
         Text = " 'Control_Invoke' example ";
         myDelegate = new AddListItem(AddListItemMethod);
      }
      static void Main()
      {
         MyFormControl myForm = new MyFormControl();
         myForm.ShowDialog();
      }
      public void AddListItemMethod(String myString)
      {
            myListBox.Items.Add(myString);
      }
      private void Button_Click(object sender, EventArgs e)
      {
         myThread = new Thread(new ThreadStart(ThreadFunction));
         myThread.Start();
      }
      private void ThreadFunction()
      {
         MyThreadClass myThreadClassObject  = new MyThreadClass(this);
         myThreadClassObject.Run();
      }
   }
   public class MyThreadClass
   {
      MyFormControl myFormControl1;
      public MyThreadClass(MyFormControl myForm)
      {
         myFormControl1 = myForm;
      }
      String myString;

      public void Run()
      {

         for (int i = 1; i <= 5; i++)
         {
            myString = "Step number " + i.ToString() + " executed";
            Thread.Sleep(1000);

            myFormControl1.Invoke(myFormControl1.myDelegate,
                                   new Object[] {myString});
         }
      }
   }

위의 내용을 살펴보면 상단에 AddListItem이라는 string을 파라미터로 가지고 있는 대리자를 선언해주었고 해당 대리자로 myDelegate라는 객체를 생성해놓았다. 그리고 Run()이라는 함수가 실행될 때 for문을 통해 총 5번을 myListBox의 Item을 추가해주는 방식이다. 여기서 포인트로 봐야할 점은 Run()에 Thread.Sleep(1000)이 있는데 Invoke는 동기식 방법이기때문에 1초를 대기 후 해당 작업을 실행하는 것을 중점으로 보면 될 것 같다.

 


 

BeginInvoke란 ?

1. Control.BeginInvoke ?
> 컨트롤의 기본 핸들이 만들어진 스레드에서 대리자를 비동기적으로 실행하는 방법

2. Delegate.BeginInvoke ?
> 컨트롤의 내부 핸들이 만들어진 스레드에서 지정된 인수를 사용하여 지정된 대리자를 비동기적으로 실행하는 방법

** BeginInvoke 정리
> 컨트롤의 본인 스레드가 아닌 다른 스레드를 이용하여 해당 컨트롤 객체를 비동기식으로 실행하는 방법이다.

 

BeginInvoke 메서드 구조

public IAsyncResult BeginInvoke(
        Delegate method, --대리자 메서드
        params Object[] args -- 대리자 파라미터 (생략 가능)
)

 

BeginInvoke 사용 예제 (Control.BeginInvoke)

public delegate void MyDelegate(Label myControl, string myArg2);

private void Button_Click(object sender, EventArgs e)
{
   object[] myArray = new object[2];

   myArray[0] = new Label();
   myArray[1] = "Enter a Value";
   myTextBox.BeginInvoke(new MyDelegate(DelegateMethod), myArray);
}

public void DelegateMethod(Label myControl, string myCaption)
{
   myControl.Location = new Point(16,16);
   myControl.Size = new Size(80, 25);
   myControl.Text = myCaption;
   this.Controls.Add(myControl);
}

위의 예제 소스처럼 myTextbox에 myControl이라는 라벨을 넣게 될 때 BeginInvoke를 사용하여 두개의 컨트롤 스레드의 교착상태를 방지하여 사용한 예제이다. 

 

 

 

글을 마치며..

이런식으로 동기와 비동기를 구분지어 상황에 맞게 Invoke와 BeginInvoke를 사용하면 될 것이다. 위의 예제는 MSDN에서 참조한 것으로 아주 간단한 예제이지만 실제 현업에서 사용할 경우 더 복잡한 로직을 처리해야하는 경우가 다반사 일 것이다. 이 글을 정리 전 나는 뭣도 모르고 BeginInvoke만 사용을 해왔지만 Invoke의 예제와 같이 동기식으로 하나의 기능이 끝난 후 처리를 하고 싶다면 Invoke를 사용하는 방식도 상황에 맞게 잘 활용 하면 좋을 것 같다.

 

 

참조 URL

댓글

💲 많이 본 글