위와 같이 디버그 모드로 실행 중 Break All 버튼을 누르면 메인쓰레드에서 Join()함수 라인에서 멈춰있는 것을 볼 수 있다.
그리고 test로 이름지어준 쓰레드로 바꿔주면 백그라운드에서 실행중인 MainThread() 부분에 화살표가 이동하는 것을 볼 수 있다.
일감(콜백함수)을 던져주면 기다리고 있던 직원(쓰레드)이 available할때 일감을 처리하고는 다시 쓰레드풀에 돌아가서 대기한다.
대기하고 있는 직원의 수가 10명이면, 10명까지만 일처리를 하러 나갈 수 있다. 즉 더 이상 직원의 수를 늘리지 않고 일처리가 끝날때까지 대기했다가 직원이 일처리를 마치고 돌아오면 그때 그 다음 작업을 할당받는 식으로 동작한다.
하지만 그 일이 아주 길거나 끝나지 않는다면.. 통째로 먹통이 될 위험이 있기 때문에 짧은 일감을 줄때 쓰레드풀을 사용하는 것을 권장한다고 한다.
for문에서 i<5로 했을 경우 직원 5명이 전부 작업을 나갔다가 과로사로 운명을 달리해 인력사무소가 망해버렸기 때문에 그 다음 코드를 실행하지 못해 "쓰레드야 안녕!" 문장이 출력되지 않는 것을 보여준다.
하지만 i<4로 했을 경우, 1명이 남아있기 때문에 MainThread라는 일감을 수행하게 되고 정상적으로 출력이 되는 것을 볼 수 있다.
쓰레드풀에서 뽑아서 쓰는게 아닌 별도 작업 단위이다. 쓰레드풀은 딱 1개만 있는데 Task가 일감(아래 코드에선 ... new Task(() => { 일감 }을 생성해 던져주면 쓰레드풀에서 대기중인 쓰레드가 그 일감을 실행시켜주는 것이다.
Task 객체를 생성할때 별도로 LongRunning 옵션을 통해 오래 걸리는 작업이 될 것이라는 것을 알려주면 쓰레드풀에서 직원을 뽑아쓰지 않고 별도로 실행해버린다. 그래서 위 코드에서 for문에 i<5임에도 불구하고 MainThread()가 호출되는 것을 볼 수 있다.
하지만 LongRunning을 넣어주지 않는다면, 작성코드#2의 경우처럼 다같이 망해버리는 사태가 발생한다.
class Program
{
static void MainThread(object? state)
{
for (int i = 0; i < 5; i++)
Console.WriteLine("Hello Thread!");
}
static void Main(string[] args)
{
ThreadPool.SetMinThreads(1, 1);
ThreadPool.SetMaxThreads(5, 5);
for (int i = 0; i < 4; i++)
{
Task t = new Task(() => { Console.WriteLine("난 Task"); }, TaskCreationOptions.LongRunning);
t.Start();
}
ThreadPool.QueueUserWorkItem(MainThread);
while (true) { }
}
}