본문 바로가기
[ Program ]/c#스터디

58. Threadpool / BackgroundWorker / Parallel

by 관이119 2022. 5. 11.

Thread 와 task 를 주로 설명하고 있는데 Threadpool 이라는 클래스로 쓰레드를 다룰수도 있다.

아래글을 먼저 읽어보자.

 

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=hyungjoon_&logNo=221700870194 

 

Thread vs ThreadPool vs Task 닷넷 C#

마이크로소프트 .net framework는 동시성을 다루기 위한 3가지 방법이 있다. 1. 스레드(Thread) 에 직접접...

blog.naver.com

다른블로그들에서도 거의 task 를 추천하는 사람이 많고 내생각도 비슷하다.

개인적으로는 Thread 자체를 쓰는것도 나쁘지는 않다고 생각한다.

하지만 Threadpool 클래스는 사용을 지양하는 것이 좋은것 같다.

경험상 Threadpool 클래스를 많이 사용하는 소스에서는 종종 이상동작이 발견되곤 했는데 쓰레드라서 디버깅도 힘들고 어디서 문제가 발생됐는지 찾기가 쉽지않았다.

그런데 비슷한 내용을 Thread 클래스를 사용했을때는 발생하지 않아서 개인적으로는 Threadpool 을 전혀 사용하지 않고 있다.

Task 역시 그런문제가 발생하지 않았는데 정확한 원인을 밝히지는 못했지만 Threadpool 클래스자체는 상당히 불안정한 느낌이다.

그래도 사용해놓은 부분이 있으면 내용파악은 할수 있어야하기때문에 사용법만 간단하게 설명하고 넘어가겠다.

 

다음은 예제로 사용한 소스이다.

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
67
68
69
70
71
72
73
74
75
76
#define type1
 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace WindowsFormsApp13
{
    public partial class Form10 : Form
    {
        public Form10()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
 
            //WaitCallback
 
            object ob = new object();
 
#if type1
            ThreadPool.QueueUserWorkItem(labelchangethread);
#elif type2
            ThreadPool.QueueUserWorkItem((object obj) => labelchangethread(obj) , ob);
#elif type3
            ThreadPool.QueueUserWorkItem((o) => labelchangethread(o), ob);
#endif
        }
 
        public void labelchangethread(object obj)
        {
            for (int i = 1; i <= 500; i++)
            {
                Thread.Sleep(10);
                label_text_change(label1, i.ToString());
            }
 
            label_text_change(label1, new Random().Next(500).ToString());
 
        }
 
        public void label_text_change(Label lb, string i)
        {
            if (lb.InvokeRequired)
            {
#if type1
                Action ac = new Action(delegate () { lb.Text = i; });
                lb.Invoke(ac);
#elif type2
                lb.Invoke(new Action(delegate () { lb.Text = i; }));
#elif type3
                lb.Invoke((Action)delegate () { lb.Text = i; });
#elif type4
                lb.Invoke((MethodInvoker)delegate () { lb.Text = i; });
#elif type5
                lb.Invoke((Action<string>)delegate (string s) { lb.Text = s; }, i);
 
#endif
            }
            else
            {
                lb.Text = i;
            }
        }
    }
}
 
cs

 

ThreadPool.QueueUserWorkItem 라는 함수로 쓰레드 풀에 사용할 함수를 담아둘수 있다.

 

그림1

 

QueueUserWorkItem  은 그림1과 같이 설명되어있는데 큐에 담아두면 순차적으로 큐에 담긴 함수들을 실행시킨다는 의미로 이해하면 되겠다.

 

파라미터로 받는 WaitCallback 같은경우는 그림2와 같이 설명되어있다.

그림2

object 형태를 파라미터로 받는 델리게이트라는 의미이다.

그래서 위 소스 예제처럼 type1,2,3 형태로 호출한것이다. 물론 3가지는 같은내용을 의미한다.

 

다시 이야기하지만 Threadpool  보다는 Task 나 Thread 를 사용하도록하자.

 

BackgroundWorker 클래스를 사용하여 비동기 프로그램을 만들수도 있다.

아래 예제를 보자.

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
67
68
69
70
71
72
#define type1
 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace WindowsFormsApp13
{
    public partial class Form11 : Form
    {
        public Form11()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            BackgroundWorker bg = new BackgroundWorker();
            bg.DoWork += Bg_DoWork;
            bg.RunWorkerAsync();
        }
 
        private void Bg_DoWork(object sender, DoWorkEventArgs e)
        {
            labelchange();
        }
 
        public void labelchange()
        {
            for (int i = 1; i <= 500; i++)
            {
                Thread.Sleep(10);
                label_text_change(label1, i.ToString());
            }
 
            label_text_change(label1, new Random().Next(500).ToString());
 
        }
 
        public void label_text_change(Label lb, string i)
        {
            if (lb.InvokeRequired)
            {
#if type1
                Action ac = new Action(delegate () { lb.Text = i; });
                lb.Invoke(ac);
#elif type2
                lb.Invoke(new Action(delegate () { lb.Text = i; }));
#elif type3
                lb.Invoke((Action)delegate () { lb.Text = i; });
#elif type4
                lb.Invoke((MethodInvoker)delegate () { lb.Text = i; });
#elif type5
                lb.Invoke((Action<string>)delegate (string s) { lb.Text = s; }, i);
 
#endif
            }
            else
            {
                lb.Text = i;
            }
        }
    }
}
 
cs

특별한내용은 없고 인스턴스를 생성한 후 Dowork 이벤트에 행동만 추가해주고 RunWorkerAsync 만 실행해주면 된다.

이클래스 역시 예전에 사용하던 클래스로 현재는 Thread 나 Task 를 사용하는게 더 좋다.

 

 

Parallel 라는 클래스도 있다.

이클래스 역시 비동기 분산 관련 클래스 인데 일단 예제를 보자.

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
#define type1
 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace WindowsFormsApp13
{
    public partial class Form11 : Form
    {
        public int _startnum = 1;
        public int _endnum = 1000;
 
        public Form11()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            DateTime startdt = DateTime.Now;
 
            int result = 0;
 
            Parallel.For(_startnum, _endnum, (i) => { result += i; });
 
            DateTime enddt = DateTime.Now;
            TimeSpan ts = enddt - startdt;
 
            MessageBox.Show($"{result.ToString()} , { ts.TotalMilliseconds.ToString()}");
        }
 
        private void button2_Click(object sender, EventArgs e)
        {
            DateTime startdt = DateTime.Now;
 
            int result = 0;
 
            for(int i= _startnum;i<_endnum;i++)
            {
                result += i;
            }
 
            DateTime enddt = DateTime.Now;
            TimeSpan ts = enddt - startdt;
 
            MessageBox.Show($"{result.ToString()} , { ts.TotalMilliseconds.ToString()}");
        }
    }
}
 
cs

버튼1같은경우는 Parallel 로 만든예제이고 버튼2는 일반적인 for 문으로 만든예제이다.

1부터 해당숫자까지 각각의 지역변수 result 에 더해서 출력하는 내용인데 실행해보면 버튼2가 더 시간이 작게 걸린다

작은작업은 그래도 괜찬은데 _endnum 을 큰수로 바꿔서 테스트해보자.

버튼1을 클릭할때마다 합계숫자가 바뀌는것을 알수 있다.

내부적으로 어떤작업을하는지 정확히 알순없지만 뭔가 비정상작동하는걸 알 수 있다.

또한 처리 시간도 더 많이 걸리고 있다.

내부적으로 여러쓰레드를 사용해서 이상작동한다고 생각할수 있지만 그럴경우 아예 저런형태로 사용하지 못하게 에러를 내야하는게 맞다고 생각한다.

개인적으로 이렇게 테스트해서 이상작동하는 클래스는 사용하지않는편이라서 이클래스는 비사용을 권장한다.

 

사실 button1 의 내용을 아래와 같이 바꾸면 정상작동가능하기는 하다.

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
private void button1_Click(object sender, EventArgs e)
{
    DateTime startdt = DateTime.Now;
 
    long result = 0;
 
    object lockObject = new object();
 
    Parallel.For(_startnum, _endnum, (i) => {
    Monitor.Enter(lockObject);
    try
    {
        result += i;
    }
    finally
    {
        Monitor.Exit(lockObject);
    }
    
    });
 
    DateTime enddt = DateTime.Now;
    TimeSpan ts = enddt - startdt;
 
    MessageBox.Show($"{result.ToString()} , { ts.TotalMilliseconds.ToString()}");
}
cs

하지만 수행시간은 여전히 for문에 비해 전체적으로 오래 걸려서 사용에대한 필요성을 느끼지못하는건 동일하다

이코드가 아닌 다른형태에서는 일부 유리할수도 있을수 있으나 여러쓰레드로 내부적으로 처리하는만큼 신경써줘야 할 부분들도 많다.

개인적으로는 이래저래 마음에 들지 않는 클래스이긴한데 꼭 사용할려면 내부적으로 여러 쓰레드가 돈다는걸 인지하고 사용하도록 하다.

 

이장에서 설명한 클래스들은 왠만하면 사용하지말고 Thread 와 Task 로 바꿔서 사용하도록 하자.

 

 

***숙제 : 이전작업에 Threadpool / BackgroundWorker / Parallel 를 사용한 코드가 있는지 확인해보고 어떻게 바꾸면 좋을지 생각해보자.

'[ Program ] > c#스터디' 카테고리의 다른 글

60. 유저컨트롤  (0) 2022.05.17
59. lock / Monitor / Mutex  (0) 2022.05.11
57. async / await  (0) 2022.03.29
56. 쓰레드2(Task)  (0) 2022.02.27
55. Action/Func  (0) 2022.02.22

댓글