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

56. 쓰레드2(Task)

by 관이119 2022. 2. 27.

앞장들에서 배운 Thread 외에도 비교적 최근에 만들어진 Task 클래스로도 멀티쓰레드 프로그램을 만들수 있다.

둘의 차이점은 thread 는 비교적 큰 작업에 사용하고 메인프로세스가 종료되어도 계속 작동하고 , Task 는 비교적 작은 작업에 사용하고 메인 프로그세스가 종료되면 같이 종료 된다 라고 대부분의 글에 되어있다.

개인적으로는 어떤걸 써도 편하고 실수 덜 할만한걸로 사용하는게 좋다고 생각한다.

 

쓰레드와 태스크는 위에서 말한거 외에도 좀 다른점이 있긴하다.

 

그림1
그림2

 

그림1과 그림2를 비교해보면 그림2가 확실히 자원을 덜 먹는것을 알수 있다.

위쪽은 대량의 쓰레드를 생성하는것이고 아래쪽은 같은작업을 태스크로 한것인데 , 일단 예제부터가 사실 말이 안되긴한다.

프로그램에서 저정도의 쓰레드를 돌릴경우가 과연있을까 생각해보면 게임개발 말고는 없는것같다.

심지어 게임에서도 과연 저정도의 쓰레드를 사용할 일이 있을까 의문이 든다.

아무튼 무리한 말도안되는 예제로 비교해보면 쓰레드가 피씨의 자원을 상대적으로 더 많이 먹는다는 것을 알수 있다.

둘의 대표적인 차이점은 쓰레드 풀을 이용하는것과 그렇지않다는것이다.

 

그림3

쓰레드가 여러가지 작업을 조금씩 수행해서 동시에 실행되는거처럼 보인다는 이야기는 이미 했다.

그림3을 보자.

그림3에서 동그라미 하나가 각각의 쓰레드라고 생각하고 1번조금실행되고 2번조금실행되고 이런형태로 작업된다고 생각하면된다.

4번에서 쓰레드풀이라는 하나의 네모박스에는 여러개의 쓰레드가 들어있는데 Task 는 저 쓰레드 풀에 쓰레드를 하나 만들어서 넣는 작업이라고 생각하면 된다.

기존에 사용하는 쓰레드는 1,2 처럼 각각의 하나의 쓰레드를 생성하는것이 된다.

물론 번갈아가며 조금씩 실행하는 속도가 워낙빠르기때문에 거의 동시에 실행된다고 생각하면되는데 일단 쓰레드 자체는 저렇게 가지를 하나 만들어줘야해서 아무래도 테스크 보다는 피씨의 자원을 상대적으로 많이 소모하게 되는건 맞다.

아무튼 개인적으로는 어느걸 써도 상관없다고 생각하고 있으나 Task 가 뒤에 나온 클래스이고 여러문서들을 찾아보면 사용하라고 권장되어있기 때문에 왠만하면 Task 를 쓰는 방향으로 하자.

 

Task 의 경우 객체로 만들어서 사용할수도 있지만 보통은 Task 에 static 매서드로 있는 Run 을 사용한다.

기본 예제를 하나 보자.

 

그림3

그림3을 보면 3가지 경우를 올려놨다.

참고로 task1,2,3 은 다 같은행동을 한다.

물론 개인의 취향에 따라 여러가지를 사용할수 있겠지만 대체로 task1 타입으로 많이 사용하는편이다.

그리고 task2 스타일의 경우는 여러 글들을 찾아보면 비권장사항으로 되어있다.

궁금하면 Task.Run 과 Task.Factory.StartNew 가 어떻게 다른지 찾아보자.

간단하게 설명하면 Task.Factory.StartNew 의 여러가지 옵션중 최적의 옵션으로 실행하게 해놓은 함수가 Task.Run 이다.

Task.Factory.StartNew 는 그외 내부적인 여러가지 문제들이 있다고 하니 최대한 사용을 지양하자.

task3 스타일의 경우는 task1 스타일로도 가능한데 구지 저렇게 길게 늘여쓰지않아 대체로 사용하지 않는다.

결국 task1 스타일로 대부분 사용한다고 생각하면 된다.

​​Task.Run 은 Action 을 파라미터로 받는데 위 그림3에 예제들이 나와있으므로 잘 살펴보기 바란다.

 

Task 는 실제 컨트롤들을 사용해 예제로 상세히 살펴보자.

프로그램에서 내가 텍스트 박스에 숫자를 입력하고 버튼을 누르면 메인창과 상관없이 작동하는 예제를 만들어보며 전체내용을 알아보자.

그림4

기본 UI 는 그림4와 같이 만들었다.

텍스트박스에 숫자를 입력하고 버튼을 누르면 그 숫자부터 1000 까지 label 에 실시간으로 바뀌면서 보여주는 예제를 만들어보자.

먼저 task 를 사용하여 작업을 하고 뒤에 쓰레드로 만들어서 비교도 해보자.

 

 

 

그림5

 

그림5를 보면 소스를 일단 저렇게 만들었다.

내용은 일일이 설명하지 않아도 알거라고 생각한다. 

그런데 이렇게 하면 작동할까?

실행시켜보면 이상한 에러가 뜬다.

 

 

그림6

 

그림6과 같이 크로스 스레드 작업이 잘못되었다면서 에러가 발생해 버린다.

 

 

그림7

 

그림7을 보면 그 내용을 알수 있다.

우리가 위 소스에서 label 의 값을 변경하라고 입력한 내용은 새로 생선된 쓰레드에서 지시하는 내용이다.

하지만 실제 컨트롤은 메인쓰레드에 포함 되어있다.

자신의 쓰레드가아닌 다른쓰레드에 포함되어있는 객체의 값을 변경하라고 하면 크로스쓰레드오류가 발생하는데 이건 어찌보면 당연한 내용이다.

자신의 쓰레드에서 어떤요청이 들어올지 알수없는 상태에서 다른쓰레드의 변경요청까지 받아야한다면 동시에 변경요청을 받는 문제가 생길 수 있기 때문이다.

그래서 이렇게 나자신의 쓰레드에 속한 객체가 아닌 다른쓰레드에 속한 객체의 내용을 변경하고 싶을때는 InvokeRequired 라는 함수로 내용변경이 가능한지 확인하고 변경을 해줘야 한다.

 

 

그림8
그림9

 

그림8을 보면 그림6의 textchange 함수를 작동하게 변경한 예제가 있다.

그림9에 해당메서드의 설명이 나와있는데 결국 대리자를 통해서 해당컨트롤의 값을 변경해야하면 true가 반환 된다는 이야기다.

위의 예제의 경우 실제로 서로다른쓰레드이기때문에 항상 true 만 반환되어 else { label1.text = i.ToString(); } 부분이 필요없는 상태인데 이함수를 메인쓰레드에서 사용할수도 있기때문에 공용으로 사용하는 차원에서 else 부분을 넣어놨다.

그림8에서 InvokeRequired 가 true 일때는 대리자로 호출해야 하기때문에 invoke 함수를 사용해야한다.

invoke 함수는 몇가지 타입으로 구분해놨는데 하나씩 살펴보자. ( 물론 type1 ,2,3,4 는 동일하게 동작한다. )

Type1 같은경우는 델리게이트를 생성하여 label1 의 값을 변경하게 했다.

가장기본적인 형태인데 Type2처럼 앞장에서 배운 Action 을 사용할수도 있다.

Action 역시 미리 닷넷에서 정의해놓은 델리게이트이기때문에 아무 문제가 없다.

label 의 값을 변경해줘야 하기때문에 값변경을 위한 파라미터가 하나 필요해서 Action<string> 으로 만들었다.

두개의 값이 필요하면 Action<string,string> 으로 해주면 된다.

Type3 같은 경우는 무명메서드를 Action 형태로 형교환해서 호출해준 내용인데 형교환 없이 호출하면 이상한 에러가 발생한다.

 

그림10

 

그림10 이 그내용인데 결론적으로말하면 무명메서드를 사용할경우 형교환을 해주거나 아예 action 을 쓰는게 낫다.

누군가 쓴글들을 읽어보기도 했는데 사실 정확히 이해하지 못했다.

최종적으로 내린 결론은 delegate 키워드로 생성된 무언가를 대신할때는 무명메서드를 바로 사용할수 있지만 Delegate 클래스를 사용하는 곳에는 형교환이 꼭 필요하다는 결론을 얻었다.

무명메서드를 형교환 해야 한다면 람다식또한 마찬가지로 형교환을 해줘야 한다.

그래서 그림8의 type3,type4,type5 는 전부 형교환을 해준내용이다.

참고로 아래 포스트도 읽어보자.

 

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

 

[끄적노트] Delegate와 delegate 그대들은 누구인고?

오랜만에 블로그에 쓰는 글인 것 같다.... 갑자기 생각난 김에...정리도 할겸... 여튼, C#에서 Invoke 호...

blog.naver.com

 

 

[[[ 사용된 소스 ]]]

#define type3

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 WindowsFormsApp13
{
    public partial class Form7 : Form
    {
        public Form7()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            int startnum = int.Parse(textBox1.Text);

            Task.Run(() => textchange(startnum));
        }

        public void textchange(int startnum)
        {
            for (int i = startnum; i < 100000; i++)
            {
                if (label1.InvokeRequired)
                {

#if type1 //델리게이트를 사용
                    lbltxt lb = new lbltxt(lbltxt_function);
                    label1.Invoke(lb, i.ToString());

#elif type2 //Action 을 사용
                    Action<string> ac = new Action<string>(lbltxt_function);
                    label1.Invoke(ac, i.ToString());

#elif type3 //무명메서드를 Action 으로 형변환하여 사용
                    label1.Invoke((Action<string>)(delegate (string s) { label1.Text = s; }), i.ToString());

#elif type4 //무명메서드를 지정델리게이트로 형변환하여 사용
                    label1.Invoke((lbltxt)(delegate (string s) { label1.Text = s; }), i.ToString());

#elif type5 //람다식을 Action으로 형변환하여 사용
                    label1.Invoke((Action<string>)((s)=> { label1.Text = s; }), i.ToString());

#endif
                    /*
                    lbltxt rt = new lbltxt(lbltxt_function);
                    Delegate dg = rt;k 
                    Invoke(dg);
                    */
                }
                else
                {
                    label1.Text = i.ToString();
                }
            }
        }

        delegate void lbltxt(string str);

        public void lbltxt_function(string str)
        {
            label1.Text = str;
        }
    }
}

 

 

***숙제 : 버튼을 누르면 숫자가 1부터시작해서 45까지 변한후 랜덤숫자로 변하는 로또 추첨기를 만들어보자.

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

58. Threadpool / BackgroundWorker / Parallel  (0) 2022.05.11
57. async / await  (0) 2022.03.29
55. Action/Func  (0) 2022.02.22
54. 람다식  (0) 2022.02.17
53. 무명메서드  (0) 2022.02.08

댓글