Programming/C#

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

BadaGreen_Kim 2018. 8. 29. 14:53

C# 멀티쓰레드와 Invoke에 관해 정리를 잘해놓으신분이 있어서 C# Multi Thread와 Invoke에 관해 포스팅한다.

 

출처 : http://ddochea.tistory.com/11?category=568955  [또치의 삽질 보관함]

 

 

 

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

 

 


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

 

 

 

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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 MultiThread
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            long cnt = 0;
 
            if(long.TryParse(textBox1.Text,out cnt))
            {
                label2.Text = Calc(cnt).ToString();
            }
            else
            {
                label2.Text = "지정한 숫자가 잘못되었습니다.";
            }
 
 
        }
 
        private long Calc(long cnt)
        {
            long result = 0;
 
            for(long i=0; i< cnt; i++)
            {
                result += 2;
            }
 
            return result;
 
        }
 
 
    }
}
 
cs

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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 MultiThread
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            long cnt = 0;
 
            if(long.TryParse(textBox1.Text,out cnt))
            {
                Thread thread = new Thread(new ThreadStart(delegate ()
                {
                    var result = Calc(cnt);
                    label2.Text = result.ToString();
                }));
                thread.Start();
            }
            else
            {
                label2.Text = "지정한 숫자가 잘못되었습니다.";
            }
 
 
        }
 
        private long Calc(long cnt)
        {
            long result = 0;
 
            for(long i=0; i< cnt; i++)
            {
                result += 2;
            }
 
            return result;
 
 
cs

 

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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 MultiThread
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            long cnt = 0;
 
            if(long.TryParse(textBox1.Text,out cnt))
            {
                Thread thread = new Thread(new ThreadStart(delegate ()
                {
                    var result = Calc(cnt);
 
 
                    this.Invoke(new Action(delegate ()
                    {
                        label2.Text = result.ToString();
                    }));
 
                }));
                thread.Start();
            }
            else
            {
                label2.Text = "지정한 숫자가 잘못되었습니다.";
            }
 
 
        }
 
        private long Calc(long cnt)
        {
            long result = 0;
 
            for(long i=0; i< cnt; i++)
            {
                result += 2;
            }
 
            return result;
 
        }
 
 
    }
 
cs

 

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

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