서론
이번 글은 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
'Programming > C#' 카테고리의 다른 글
C# Datagridview 특정 행, 열의 색상 바꾸는 방법 (0) | 2022.03.29 |
---|---|
C# DNS 또는 IP를 이용한 Socket IP 설정 방법 (IPAdrress / IPEndPoint 설정) (0) | 2022.03.18 |
C# 박싱과 언박싱이란? (개념 / 예제 / 사용 이유) (2) | 2022.01.16 |
C# .Net Framework와 .Net Core 5.0 어셈블리(dll) 참조하는 방법 (0) | 2022.01.06 |
C# Properties.Settings.Default의 설정 값은 어디에 저장이 될까? (app.config 관리) (0) | 2022.01.06 |
댓글