본문 바로가기
개발언어/C#

[C#] 다른 Thread에서 UI접근하기

by 창용이랑 2021. 1. 18.
728x90
async / await를 이용한 보다 간편한 UI 접근방법을 포스트로 작성하였습니다.
해당 포스트와 함께 https://ddochea.tistory.com/40 를 함께 참고해주시기 바랍니다.

오랜 시간이 걸리는 작업에 대해선 Thread를 생성하여 처리하는 것은 어느 프로그램이나 마찬가지일 것이다. C# 기반 프로그램도 예외는 아니다. 만일 Thread 없이 만들면 어떻게 될까? Thread를 선언하지 않은 상태에서 아래와 같은 프로그램을 만들어보았다. 해당 프로그램은 입력한 수 x 만큼 2를 더하는 프로그램. 즉, x*2를 덧셈만으로 계산하는 프로그램이다.

 

[그림 1] 입력한 수 x 만큼 2를 더하는 프로그램

다음 프로그램의 소스는 아래와 같다.

 

using System; 
using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

namespace InvokeExample 
{ 
	public partial class Form1 : Form 
	{ 
		public Form1() 
		{ 
			InitializeComponent(); 
		} 
		
		private void btnCalc_Click(object sender, EventArgs e) 
		{ 
			long cnt = 0; 
			if (long.TryParse(txt_X.Text, out cnt)) 
			{ 
				lbl_Result.Text = Calc(cnt).ToString(); 
			} else { 
				lbl_Result.Text = "지정한 숫자가 잘못되었습니다."; 
			} 
		} 
		
		private long Calc(long cnt) 
		{ 
			long result = 0; 
			for (long i = 0; i < cnt; i++) 
			{ 
				result += 2; 
			} 
			return result; 
		} 
	} 
}

해당 프로그램을 빌드한 후 실행하면 에러없이 반복덧셈 프로그램이 실행될 것이다. 실행환경에 차이를 보이겠지만, x 입력란에 999999999 정도 입력하면 프로그램이 일시적으로 멈추는 현상이 나타나게 될 것이다. 원인은 버튼을 누르는 순간, 반복덧셈작업을 실행하게 되는데 스레드를 따로 분리하지 않고 계산하기때문에 계산하는동안 UI 핸들링을 담당하는 MainThread가 UI 컨트롤을 핸들링하지 못하기 때문이다. 그래서 UI가 멈추게 되는 것이다. 이런 현상이 장시간 지속되면 프로그램은 응답 대기 메시지를 띄워 종료여부를 확인하거나, 심할 경우 프로그램이 비정상적으로 종료될 수 있다. 이를 해결하기위해 thread 사용하게 되는데 아래와 같이 코딩하여 실행하면 크로스 스레드(Cross Thread) 관련 Exception을 보게 된다.

 

using System; 
using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.Linq; 
using System.Text; 
using System.Threading; // 추가된 namespace
using System.Threading.Tasks; 
using System.Windows.Forms; 

namespace InvokeExample 
{ 
	public partial class Form1 : Form 
	{ 
		public Form1() 
		{ 
			InitializeComponent(); 
		} 
		
		private void btnCalc_Click(object sender, EventArgs e) 
		{ 
			long cnt = 0; 
			if (long.TryParse(txt_X.Text, out cnt)) 
			{ 
				Thread thread = new Thread(new ThreadStart(delegate() // thread 생성 
				{ 
					// ThreadStart 델리게이트를 통해 해당 Thread 가 실행할 작업(Method)을 위임(delegate) 함. 
					
					var result = Calc(cnt); 
					lbl_Result.Text = result.ToString(); // error!! 크로스 스레드 작업 
				})); 
				
				thread.Start(); // thread 실행하여 병렬작업 시작

			} else { 
				lbl_Result.Text = "지정한 숫자가 잘못되었습니다."; 
			} 
		} 
		
		private long Calc(long cnt) 
		{ 
			long result = 0; 
			for (long i = 0; i < cnt; i++) 
			{ 
				result += 2; 
			} 
			return result; 
		} 
	} 
}

이를 해결하기위한 것이 Invoke 메소드이다. Control의 Invoke는 다른 스레드에서 직접 접근할 수 없는 윈폼 컨트롤 작업에 대해 Invoke를 통해 작업 자체를 위임하여 MainThread가 해당 작업을 실행하게 한다. 아래 예제를 통해 크로스 스레드 작업 오류에 대한 해결방법을 확인해보자.

using System; 
using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.Linq; 
using System.Text; 
using System.Threading; // 추가된 namespace
using System.Threading.Tasks; 
using System.Windows.Forms; 

namespace InvokeExample 
{ 
	public partial class Form1 : Form 
	{ 
		public Form1() 
		{ 
			InitializeComponent(); 
		} 
		
		private void btnCalc_Click(object sender, EventArgs e) 
		{ 
			long cnt = 0; 
			if (long.TryParse(txt_X.Text, out cnt)) 
			{ 
				Thread thread = new Thread(new ThreadStart(delegate() // thread 생성 
				{ 
					// ThreadStart 델리게이트를 통해 해당 Thread 가 실행할 작업내용을 선언 
					// ThreadStart는 Java의 Runnable 인터페이스와 비슷한 개념이다. 
					
					var result = Calc(cnt); 
					
					// this == Form 이다. Form이 아닌 컨트롤의 Invoke를 직접호출해도 무방하다. 
					this.Invoke(new Action(delegate() 
					{ 
						//Invoke를 통해 lbl_Result 컨트롤에 결과값을 업데이트한다. 
						lbl_Result.Text = result.ToString(); 
					}));
				})); 
				
				thread.Start(); // thread 실행하여 병렬작업 시작

			} else { 
				lbl_Result.Text = "지정한 숫자가 잘못되었습니다."; 
			} 
		} 
		
		private long Calc(long cnt) 
		{ 
			long result = 0; 
			for (long i = 0; i < cnt; i++) 
			{ 
				result += 2; 
			} 
			return result; 
		} 
	} 
}

 핵심(아래처럼하면..다른 스레드에서 윈폼에 접근 가능하다)
 
 this.Invoke(new Action(delegate() 
 {  
 	lbl_Result.Text = result.ToString(); 
 }));
				

이것으로 Invoke에 대해 정리해보았다.

 

Note 1. 델리게이트(Delegate)란 쉽게 이해하자면, 메소드를 변수처럼 사용한다고 보면 된다. Thread ThreadStart 나 Invoke의 매개 변수인 Action도 델리게이트이다.

Note 2. 무명메소드는 C# 2.0이상에서 사용할 수 있으며, 델리게이트를 통해 인스턴스화 시킬 메소드를 따로 작성하지 않고, 간단하게 사용할 수 있는 편의성을 제공한다. C# 3.0 이상부터는 람다식을 통해 무명메소드의 역할을 사용할 수 있다.

(무명메소드 부가 설명 : http://msdn.microsoft.com/ko-kr/library/0yw3tz5k.aspx)



출처: https://ddochea.tistory.com/11 [또치의 삽질 보관함]

 

[Invoke & BeginInvoke] 1. 다른 Thread 에서 UI 접근하기 (1)

async / await를 이용한 보다 간편한 UI 접근방법을 포스트로 작성하였습니다. 해당 포스트와 함께 https://ddochea.tistory.com/40 를 함께 참고해주시기 바랍니다. 오랜 시간이 걸리는 작업에 대해선 Threa

ddochea.tistory.com

InvokeExample.zip
0.05MB