본문 바로가기
유니티 공부/C# 문법

C# - Thread vs Task 클래스 + EAP/APM/TAP

by 코딩하는 돼징 2024. 1. 19.
반응형

Thread(스레드)

CPU가상화이다. 그러므로 CPU가 없는데 많은 척하는 것이다.

Multithreading(멀티스레드)

하나의 프레스가 여러 개의 스레드를 동시에 실행하여 다중 작업을 수행하는 것을 의미한다. 이는 CPU의 가상화를 통해 마치 여러개의 CPU가 동시에 작업하는 것처럼 보이게 한다.


스레드는 정말 가벼운 자원인가?

스레드는(프로세스 보다 가볍지만)매우 무거운 리소스이다.

 

스레드를 하나 만드는데 필요한 공간비용과 시간비용

DLL(Dynamic Link Libraries)는 외부에서 호출되는 함수,데이터,리소스등을 가진 코드 및 데이터 라이브러리이다. 여러 스레드가 DLL을 사용하는 경우 동시 접근에 대한 적절한 동기화 매커니즘이 필요하므로 이로 인해 리소스 소비가 증가한다.

출처 : https://www.youtube.com/watch?v=ZUqUlZ3GjlA&list=LL&index=2&t=2798s

 

컴퓨터에서 가장 적합한 스레드의 갯수는?

CPU코어의 갯수만큼의 워킹 스레드

 

현실적으로는 더 많이 만들게 된다.


결론

가능한 Thread class를 이용하여 명시적으로 스레드를 생성하지 말 것

예외

1) 보통 스레드 우선순위가 아닌 스레드가 필요한 경우

2) 포그라운드 스레드처럼 동작하는 스레드가 필요한 경우

3) 계산 중심의 작업이 상당히 오랫동안 수행되어야 하는 경우


여러 스레드를 사용하는 이유

출처 : https://www.youtube.com/watch?v=ZUqUlZ3GjlA&list=LL&index=2&t=2798s

사용자 인터페이스(UI) 스레드가 다른작업을 한다고 바쁘면 멍때리게 된다. 그러므로 응답성을 개선해야 한다. 


계산 중심의 비동기 작업

1. CLR Threadpool

아래와 같이 코드를 작성하면 100개가 출력되지 않는다. 그 이유는 foreground Thread가 return되버리면 backgroudThread가 죽어버린다.

for(int i = 0; i < 100; i++)
{
    ThreadPool.QueueUserWorkItem((obj) =>
        Console.WriteLine(Thread.CurrentThread.ManagadThreadId));
}

이렇게 하면 전체 과정을 다 볼 수 있다.

for(int i = 0; i < 100; i++)
{
    ThreadPool.QueueUserWorkItem((obj) =>
        Console.WriteLine(Thread.CurrentThread.ManagadThreadId));
}
// 메인 스레드를 대기시킴
Console.ReadLine();

 

문제

1) 작업 완료 시점을 알 수 없음

작업이 백그라운드에서 동작하므로 작업이 완료된 시점을 알기 어렵다.

2) 작업 수행 결과를 얻어 올 수 없음

ThreadPool의 QueueUserWorkItem메서드는 작업의 반환값을 반환하지 않는다.

3) 취소 / 예외 처리

ThreadPool에서 직접적인 작업 취소 및 예외 처리가 제공되지 않는다.


2. Task

.Net FrameWork 4부터 도입된 비동기 작업을 위한 추상화 클래스이다. Task는 내부적으로 ThreadPool을 이용해서 스레딩을 관리한다. async/await패턴을 지원하여 비동기 프로그래밍을 쉽게 구현할 수 있다.

01 Task를 이용한 비동기 작업 수행

// QueueUserWorkItem과 유사 동작을 수행하는 코드 패턴

Action action = new Action(() => Console.WriteLine(Thread.CurrentThread.ManagedThreadId));

Task t = new Task(action); // Task 객체 후
t.Start(); // Start 명시적 호출

Task.Run(action); // 비동기 작업 수행

결과 값을 가져오는 Task객체 생성 및 대기

// 결과 값을 가져오는 Task 객체 생성, Sum() 호출시 예외가 발생한다면?
Task<int> t = new Task<int>(n => Sum((int)n, 100);
t.Start(); // 명시적 수행
t.Wait(); // Task 완료 대기

Console.WriteLine("The Sum is: " + t.Result); // t.Result 결과 획득

Sum(작업)이 될때까지 기다리므로 이렇게 Wait를 사용하게되면 병렬성의 장점이 없어진다. 

ContinueWith

해결 방법 다음 Task을 이어 붙이자 Sum을 시키고 그 다음 해야할 일은 이거야

// t Task가 완료되면 cwt Task를 수행한다
Task<Int32> t = Task.Run(() => Sum(CancellationToken.None, 100));

Task cwt = t.ContinueWith(
                     task => Console.WriteLine("The sum is: " + task.Result));

ContinueWith메서드를 사용하여 t가 Task가 완료된 후에 실행될 후속작업을 정의한다.


02 TaskContinationOptions을 이용한 후속 작업 제어

CancellationTokenSource cts = new CancellationTokenSource();
cts.Cancel();

Task<Int32> t = Task.Run(() => Sum(cts.Token, 100), cts.Token);

OnlyOnRanToCompletion - 성공 완료시

t.ContinueWith(
    task => Console.WriteLine("The sum is: " + task.Result),
    TaskContinuationOptions.OnlyOnRanToCompletion);

OnlyOnFaulted - 예외 발생시

t.ContinueWith(
    task => Console.WriteLine("Sum threw: " + task.Exception.InnerException),
    TaskContinuationOptions.OnlyOnFaulted);

OnlyOnCanceled - 취소한 경우

t.ContinueWith(
    task => Console.WriteLine("Sum was canceled"),
    TaskContinuationOptions.OnlyOnCanceled);

3. TaskCreationOptions.AttachedToParent

각 자식 Task를 부모 Task에 연결한다. 이렇게 하면 부모 Task가 완료될 때까지 자식 Task도 기다린다.

Task<Int32[]>parent = new Task<Int32[]>(()=>
{
    var results = new Int32[3];
    new Task(() =>
        results[0] = Sum(10), TaskCreationOptions.AttachedToParent).Start();
    new Task(() =>
        results[1] = Sum(20), TaskCreationOptions.AttachedToParent).Start();
    new Task(() =>
        results[2] = Sum(30), TaskCreationOptions.AttachedToParent).Start();
    return results;
});

var cwt = parent.ContinueWith( // parent Task가 끝나면 수행할 Task연결
    parentTask => Array.ForEach(parentTask.Result, ConsoleWriteLine));
parent.Start();

 

출처 :&nbsp;https://www.youtube.com/watch?v=ZUqUlZ3GjlA&list=LL&index=2&t=2798s


I/O 중심 비동기 작업

출처 : https://www.youtube.com/watch?v=ZUqUlZ3GjlA&list=LL&index=2&t=2798s

APM과 EAP의 문제점

작업을 시키는 부분과 작업을 결과를 받는 부분이 다르다. 그러면 작업이 시킬때 context정보가 작업이 끝난 다음에 필요할 수 있는데 이를 매개변수로 끌고오는 방법 밖에 없음

1. EAP(Event-based Asynchronous Pattern)

결과를 받는 부분에서 url의 엑세스를 하지 못한다.

private void Button_Click(object sender, RoutendEvnetArgs e)
{
    string url = "http:// ... ";
    WebClient client = new webClient();
    client.DownloadStringCompleted += clinet_DownloadStringCompleted;
    client.DownloadStringAsync(new Uri(url));
}
void client_DownloadStringCompleted(object sender,
                           DownloadStringCompletedEventArgs arg)
{
    Debug.WriteLine(arg.Result);
}

 

2. EAP and Lambda

람다 표현식을 사용하여 이벤트 핸들러를 간결하게 작성

private void Button_Click(object sender, RoutendEvnetArgs e)
{
    string url = "http:// ... ";
    WebClient client = new webClient();
    client.DownloadStringCompleted += (s,arg) => Debug.WriteLine(arg.Result);
    client.DownloadStringAsync(new Uri(url));
}
void client_DownloadStringCompleted(object sender,
                           DownloadStringCompletedEventArgs arg)
{
    Debug.WriteLine(arg.Result);
}

 

3. TAP(Task-based Asynchronous Pattern)

ContinueWith를 이용하여 Watiting을 제거할 수 있음 -> 어떤 Thread도 필요없는 waiting하지 않음

private void Button_Click(object sender, RoutendEvnetArgs e)
{
    string url = "http:// ... ";
    WebClient client = new webClient();
    Task<string> t = client.DownloadStringTaskAsync<new Uri(url));
    t.ContinueWith((prevTask) => Debug.WriteLine(prevTask.Result));
}

비동기 코드를 동기 코드처럼 쉽게 작성할 수 었을까요?

그래서 async와 await가 추가되었다.

4. Async/Await

private void Button_Click(object sender, RoutendEvnetArgs e)
{
    string url = "http:// ... ";
    WebClient client = new webClient();
    Task<string> t = await client.DownloadStringTaskAsync<new Uri(url));
    Debug.WriteLine(t);
}

await를 남용하는 경우 문제가 생긴다.

첫번째 비동기 작업이 완료될대까지 기다리고 난 후 두번째 비동기 작업이 시작된다. 

await.DownloadAsync(AURL);
    await.DownloadAsync(BURL);

해결방법

기억하자 ConfigureWait(false)를 하면 동작들이 독립적으로 수행한다.

await.DownloadAsync(AURL).ConfigureWait(false);
await.DownloadAsync(AURL).ConfigureWait(false);

 

 

 

 

 

 

본문은 https://www.youtube.com/watch?v=ZUqUlZ3GjlA&list=LL&index=2&t=2798s시청하고 작성하였습니다.

 

 

반응형

댓글