C# Task의 TaskCreationOptions별 실행 비교와
 ThreadPool의 관계
(SetMinThreads SetMaxThreads)



 이번에는 Task가 내부적으로 어떤 방식으로 동작하고 수행되는지에 대해서 알아가는 시간을 가져 보고자 한다. 지금까지 흔히 Task.Factory.Start()를 통해서 타스크를 생성하고 비 동기로 수행이 되도록 작업을 수행하여 왔지만 깊이 있게 다뤄보지는 안았었다. 그래서 비주얼 적으로 확인하기 위해 WPF 프로젝트를 만들어 확인 할 것이다. 아래 "그림1"은 WPF를 통해 Task 수행결과를 확인할 수 있는 화면을 먼저 보도록 하자.


[그림1] WPF를 통해서 Task 수행 결과를 확인하는 화면


[이번에는 프로젝트를 압축해서 다운 받을 수 있게 올려 두었으니 바로 다운받아 확인할 수 있을 것이다.

TaskTest.zip (클릭하면 다운로드 됨)]


 우선 소스에 대한 설명 보다는 프로그램 화면을 확인하면서 설명하면 소스의 전체적인 흐름을 알 수 있을 것이고 그것을 알면 코드를 보다 쉽게 이해할 수 있으리라는 생각이다. 그럼 지금부터 잘 따라워 주면 좋겠다. 아래 "표1"은 버튼에 대한 설명 및 선택 인자에 대한 간단한 화면 설명부터 시작하도록 하겠다.


 Set

 ProgressBar를 ListItem에 추가하고 Task를 생성하여 추가된 Progress에 연결하여 비 동기로 진행바를 변경 할수 있도록 세팅한다.

 Clear

 추가된 컨트롤을 ListItem에서 모두 지운다.

 다른 변수에 대해서도 초기화를 수행 한다.

 Async Processing

 Set에서 추가된 컨트롤에 연결된 Task를 활성화 시켜 비 동기 수행 작업으로 진행바를 증가 시키도록 한다. 

 Async Cancel

 수행되고 있는 Task를 취소 한다  

 Min Thread Count   취소 동시 실행 갯수 
 Max Thread Count 

 최대 동시 실행 갯수 

 Thread를 컨트롤

 Radio 버튼으로 되어 있어서 한번 선택이 되면 취소할 수 없도록 하였다.
 선택되면 TextBox를 사용할 수 있도록 활성화 된다. 

 MinWorker  Min Thread Count를 해당 숫자로 세팅한다. 
 MinIOC  Min Thread IOC Count를 해당 숫자로 세팅한다.   
 MaxWorker

 Max Thread Count를 해당 숫자로 세팅한다. 

 MaxIOC

 Max Thread IOC Count를 해당 숫자로 세팅한다. 

 Slide Control

 Set 버튼을 눌렀을 때 한번에 추가될 컨트롤 갯수 선택 할 수 있음.

 ComboBox

 선택된 값으로 Task생성 시 TaskCreationOptions으로 세팅한다.


 None
 기본 동작이 사용되도록 지정합니다.
 PreferFairness
 가능한 한 공정한 방식, 즉 일찍 예약된 작업은 일찍 실행되고 나중에 예약된 작업은 나중에 실행될 수 있는 방식으로 작업을 예약하는 TaskScheduler에 대한 힌트입니다.
 LongRunning
 작업이 장기 실행되는 정교하지 않은 작업이 되도록 지정합니다.초과 구독을 보장할 수 있는 TaskScheduler에 대한 힌트를 제공합니다.
 AttachedToParent
 작업이 작업 계층 구조의 부모에 연결되도록 지정합니다.
 DenyChildAttach
 지정 하는 InvalidOperationException 만들어진된 작업에는 자식 작업을 첨부 하려고 시도 하는 경우에 throw 됩니다.
 HideScheduler
 앰비언트 스케줄러를 현재 스케줄러에서 만들어진된 작업으로 표시 되지 않습니다.즉, StartNew 또는 Continuewith와 같은 생성된 작업을 수행 하는 작업을 볼 수 있도록 Default 현재 스케줄러로 합니다.


[표1] 화면 행위 단위 설명표


 이제 표를 확인했으니 버튼을 누르면 어떻게 동작하는지 대충 알거라 생각한다. 그렇다면 이제 다운 받은 압축 파일을 풀어서 실행해 보자. 아무 버튼이나 눌러봐도 컴퓨터에 이상이 있지는 안을것이기에 걱정하지 말고 마구마구 눌러 보자. 만약 "Async Processing"을 눌렀다면 진행바가 움직이면서 작업이 수행되는 것을 확인할 수 있을 것이다. 몇번의 클릭 만으로도 전체적인 흐름을 간략하게나마 체험적으로 알 수 있을 것이다. 그렇다면 필자의 의도대로 아래와 같은 시나리오로 한번 따라서 실행해 보기를 바란다. 만약 제대로 따라 했다면 뭔가 이상한 점을 발견할 수 있을 것이다. 


시나리오 :
 1. "None" 상태의 콤보박스에서 "Set"버튼을 누르면 10개의 진행바가 추가되며(10개 추가됨) 
 2. "Async Processing"을 누르면 진행바가 움직인다. 작업이 완료 후
 3. "Clear" 버튼을 누르면 초기화가 되고 다시 한번 더
 4. "Set"을 누르고
 5. "Async Processing" 버튼을 누른다.
 

[시나리오1] Min, Max Thread 활성화 변경 확인 시나리오


 위와 같은 시나리오와 같은 방법으로 확인해 보면 처음에는 몇개의 진행바만(테스트 환경의 CPU Core마다 다를 수 있다, 4 Core면 네개의 진행바가 변경되는 것을 확인할 수 있을 것이다. 그렇지만 실제로는 3개의 진행바만 움직이는데 그 이유는 하나의 Task가 내부적으로 계속 활성화 되어 있어서 발생하는 현상이다. (활성화된 Task는 Min Thread Count와 Max Thread Count의 값을 지속적으로 화면에 갱신하여 변화되는 값을 보여주는 작업 진행하고 있다) 3개의 진행바가 변경이 되고 있는 상태에서 완료되지 않은 다른 진행바가 순차적으로 작업이 시작되어 값이 변경 된다. 이건 내부적으로 Thread.Sleep 때문에 일어나는 현상이기도 하다. 그러면서 최종적으로는 10개의 진행바가 모두 변경(내부적으로 Task가 실행이 되고 있다.)이 되는 것을 확인할 수 있다. 이와 같이 10개의 Task가 활성화 되어 있는 상태는 ThreadPool이 내부에 관리되고 있는 동시 실행 가능한 갯수를 10개로 세팅된 상태로 변경이 된다.  이런 상태에서 "시나리오1"의 3, 4, 5를 차례로 수행하면 첫번째 실행된 행동 패턴과는 다르게 10개의 진행바가 한번에 진행되는 것을 확인 할 수 있다.


 결과적으로 비 동기 Task 수행도 내부적으로는 ThreadPool에서 관리를 받으며 수행이 된다는 것을 확인 할 수 있다. 조금더 자세한 사항은 Task.Factory를 커스터마이징 하는 포스트에서도 확인할 수 있을 것이다. 


[그림2] PreferFaimess Async Processing 한번 실행 후 Clear -> Set -> Async Processing 실행 결과 화면


 "그림2"에서와 같이 동시 실행 갯수가 10개로 같이 시작 하는것을 확인 할 수 있다. 그렇지만 이런 유형은 Thread.Sleep(1)의 구문으로 인해 발생하는 현상이다. Sleep 없이 비 동기를 실행 하면 절대적인 CPU Core수의 제한을 받게 된다. 그에 대한 확인으로 Sleep대신에 Task.SpinWait(500000)으로 수정하고 비 동기로 실행하면 위와 같은 현상과는 다르게 나타난다.


 지금까지 전체적인 흐름과 하나의 시나리오에 대해서 설명을 하게 되었고 그 이외의 다른 시나리오와 소스에 대한 설명은 다음 포스트에 이어서 하도록 하겠다.


( 무엇보다 이번 포스트는 올려 놓은 소스를 다운 받아서 직접 테스트와 디버깅을 통해 몸소 체험과 분석을 통해 체득하는 과정이 필수라고 알려주고 싶다. 눈으로만 보는것과 직접 체험해 보는건 하늘과 땅 차이만큼 많은 깨우침의 차이를 가져올거라 믿고 있다. )


소스 코드 자체에 주석과 직관적인 코딩으로 충분히 파악이 가능할 것으로 예상하므로 별도의 설명을 생략하도록 하겠습니다. 포스트의 내용이 장황한 설명 보다는 주석과 소스코드 자체 만으로도 이해할 수 있도록 하기 위해 노력하였습니다.. 실제 개발에서도 필요한 소스는 단순히 Copy & Paste 만으로도 사용할 수 있습니다. 그리고 주석을 이용해 nDoc이나 별도의 자동 Document 제작 유틸로 API 문서를 만드는 데에도 도움이 되었으면 한다. 
※ DOC에 대한 프로그램 정보 Util link

ing™       


  1. 개발미 2021.07.23 11:40

    안녕하세요. 글 잘 읽어봤습니다. 혹시 질문이 있는데. Thread.Sleep(1) 위치에 Task.Delay(1).Wait()을 하는 경우 시간이 더 오래걸리는 현상이 일어나는데 이것에 대한 이유를 모르겠어서 질문드립니다.

+ Recent posts