Thread ③ 스레드 사용법, 예제
C# 은 MSDN이 정답이다.
C# Threading 관련 기술
Thread
- System.Threading 네임스페이스에 정의되어 있다
- .NET Framework 1.0부터 사용 가능하다
- Thread에 실행될 로직을 넘겨주며, 생성한 후 명시적으로 실행 해야 한다.
- Thread는, 생성, 삭제될 때 상대적으로 많은 시스템 자원을 사용해 비효율적이다.
ThreadPool 은 Thread 를 미리 생성해 두고, 필요할 때 사용하고 반환하는 방식으로 기존 Thread 를 개선했다.
ThreadPool
- System.Threading 네임스페이스에 정의되어 있다
- .NET Framework 1.0부터 사용 가능하다
- ThreadPool은 특정 이름을 지정할 수 없다.
Join 과 같은 조정을 사용할 수 없다.
백그라운드 스레드라서 의도하지 않게 종료될 수 있다.
Task는 내부적으로는 ThreadPool을 사용하고, ThreadPool의 여러 가지 단점을 보완했다.
Task
- System.Threading.Tasks 네임스페이스에 정의되어 있다
- .NET Framework 4.0부터 사용 가능하다.
async, await
- .NET Framework 5.0부터 사용 가능하다.
- Task 키워드와 잘 호환된다.
① 스레드 실행 기본 개념
네임스페이스
System.Threading
다중 스레드 프로그래밍을 가능하게 하는 클래스와 인터페이스를 제공한다.
클래스
System.Threading.Thread
스레드를 만들고 제어하며, 해당 속성을 설정하고, 상태를 가져온다.
스레드 사용 방법
스레드 인스턴스를 생성한다.
생성자( )의 매개변수로 스레드로 동작 할 로직을 전달한다.
인스턴스를 명시적으로 실행한다.
스레드 동작 이해
프로그램이 실행되면, 프로세스는 최소 하나의 실행 흐름(스레드)을 가진다.
프로그램의 실행 시작점은 메인 함수이다.
메인 함수도 내부에 소스 코드로 이루어진 로직을 갖고 있다.
이를 한줄씩 처리 하며 실 흐름을 스레드 라고 하기에, 프로세스는 최소 하나의 실행 흐름(스레드)을 가진다.
그리고 메인 함수가 실행되기에, 이 실행 흐름을 메인 스레드라고 한다.
스레드 클래스의 인스턴스를 활용한 별도의 스레드 생성은, 이 메인 스레드 이외의 실행 흐름을 비동기 방식으로 구현하는 것이다.
② 함수의 특성과 스레드 적용
스레드는 실행 흐름이다.
이 실행 흐름은, 소스 코드로 구현된 비지니스 로직 덩어리로 일반적으로 함수 형태로 사용한다.
그렇기에 스레드의 실행에서 실행 될 로직을 함수 형태로 구현하여 스레드의 인스턴스에 전달하는 방법이 일반적이다.
스레드의 함수 사용에서 고려해야 할 부분은 두가지다.
1. 함수는 매개변수를 전달받을때, 변수 형태다.
함수를 매개변수 형태로 전달 할 수 없다.
그렇다면 어떻게 함수를 매개변수로 전달 할 수 있는가?
2. 함수는 반환값을 사용할 수 있다.
스레드 동작은 반환값을 사용하는 것이 고려되었는가?
기본 실행 구조
- Thread의 인스턴스를 생성해 인스턴스 생성자의 매개변수로, 메서드를 넘긴다.
- Thread.Start( ) 로 스레드 시작한다.
- Thread.Join( ) 으로 스레드의 동작 동료까지 대기하도록 한다.
매개변수가 없는 함수를 스레드로 동작시킬 경우
public delegate void ThreadStart( )
Thread t1 = new Thread(new ThreadStart(Run));
//Thread t1 = new(Run);
t1.Start();
매개변수가 있는 함수를 스레드로 동작시킬 경우
public delegate void ParameterizedThreadStart(object obj);
Thread t5 = new Thread(new ParameterizedThreadStart(Work));
//Thread t5 = new(Work);
t5.Start(123);
Thread.Start( ) 메서드를 호출할 때, object 형식으로 매개변수를 전달한다.
매개변수를 object 형식으로 전달하기 때문에, 여러 개 매개변수를 전달하기 위해서는 클래스나 구조체의 객체를 생성해서 전달한다.
함수의 다양한 형태
추론 가능함으로 생략 가능
using System.Threading.Thread
// 기본 형태
Thread t1 = new Thread(new ThreadStart(Run));
// 생략된 형태
Thread t1 = new Thread(new ThreadStart(Run));
Thread t1 = new Thread(Run);
Thread t1 = new (Run);
익명 함수 형태
Thread t2 = new Thread(delegate()
{
Console.WriteLine($"Anonymous 스레드ID {threadID} 카운트: {i}");
Thread.Sleep(100);
});
람다 형태
Thread t3 = new Thread(() => Run());
t3.Start();
new Thread(() => Run()).Start();
다른 클래스의 메서드 사용하는 형태
Test ins = new Test();
Thread t4 = new Thread(new ThreadStart(ins.Run));
Thread t4 = new(ins.Run);
③ 예제
스레드 기본 생성 & 스레드 시작
using System;
using System.Threading;
namespace ThreadTest
{
public class Program
{
// 스레드로 실행시킬 함수 선언
static void MainThread()
{
Console.WriteLine("Thread 동작함");
}
static void Main(string[] args)
{
Console.WriteLine("Main 동작함");
//1. Thread 이름 = new Thread(함수 이름);
Thread thread = new Thread(MainThread);
//2. Thread 시작
thread.Start();
Console.WriteLine("Main 종료됨");
}
}
}
디버거에서 동작중인 스레드 보기
- 중단점 설정
- 디버깅 시작
- 비주얼 스튜디오 상단 메뉴 ➫ Debug ➫ Windows ➫ Threads(Ctrl + Alt + H)
다양한 함수 형태로 스레드 사용 1
- join 이 사용된 경우
namespace ThreadTest
{
using System;
using System.Threading;
class Program
{
// 스레드로 실행 할 함수 선언
static void Run()
{
// 스레드 Name 설정
Thread.CurrentThread.Name = "Test";
// 스레드 ID 확인
int threadID = Thread.CurrentThread.ManagedThreadId;
// 스레드가 0.1초씩 10번 반복 카운트
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"스레드ID {threadID} 카운트: {i}");
Thread.Sleep(101);
}
}
static void Main(string[] args)
{
int threadID = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine($"Main스레드ID {threadID} 시작");
// 기본 스레드 사용법
Thread t1 = new (Run);
t1.Start();
// main 스레드
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Main스레드ID {threadID} 카운트: {i}");
Thread.Sleep(100);
}
// 익명 메서드 사용 스레드
Thread t2 = new Thread(delegate()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Anonymous 스레드ID {threadID} 카운트: {i}");
Thread.Sleep(100);
}
});
t2.Start();
// 람다 사용 스레드
Thread t3 = new Thread(() => Run());
t3.Start();
new Thread(() => Run()).Start();
// main 스레드가 t1 스레드의 로직이 종료될때까지 대기하도록 join
t1.Join();
t2.Join();
t3.Join();
// t1, t2, t3 스레드 로직 종료 후, main 스레드 종료 로직 동작
Console.WriteLine($"Main스레드ID {threadID} 종료");
}
}
}
다양한 함수 형태로 스레드 사용 2
- join 이 없 경우
using System;
using System.Threading;
namespace ThreadTest
{
class Async_Program
{
static void Main(string[] args)
{
// 메인 스레드에서 반복문 0부터 5까지 콘솔 출력
for (var i = 0; i < 5; i++)
{
Console.WriteLine("a = " + i);
Thread.Sleep(101);
}
// 첫번째 추가 스레드에서 사용될 함수
static void ThreadMethod1()
{
for (var i = 0; i < 5; i++)
{
Console.WriteLine("B = " + i);
Thread.Sleep(101);
}
}
// 첫번째 추가 스레드 생성
var thread1 = new Thread(ThreadMethod1);
// 두번째 추가 스레드 생성
var thread2 = new Thread(() =>
{
// 반복문 0부터 5까지
for (var i = 0; i < 5; i++)
{
// 콘솔 출력
Console.WriteLine("C = " + i);
Thread.Sleep(101);
}
});
// 첫번째 스레드 실행
thread1.Start();
// 두번째 스레드 실행
thread2.Start();
// 메인이 스레드를 기다리지 않는다.
Console.WriteLine("Press Any key...");
// 스레드 동작중에, 입력 대기 상태가 밀린다.
Console.ReadLine();
}
}
}
다양한 함수 형태로 스레드 사용 3
- join 이 없 경우
using System;
using System.Threading;
namespace Example
{
class Program
{
// 실행 함수
static void Main(string[] args)
{
// 변수
var sum = 0;
// 스래스 생성
var thread1 = new Thread(() =>
{
// 반복문 0부터 999까지
for (var i = 0; i < 1000; i++)
{
// 변수에 더한다.
sum += i;
}
});
// 스레드 시작
thread1.Start();
// 스레드가 종료할 때까지 프로세스를 중지
// join 이 없으면 결과값은 0이다.
//thread1.Join();
// 콘솔 출력
Console.WriteLine("Sum = " + sum);
// 아무 키나 누르면 종료
Console.WriteLine("Press Any key...");
Console.ReadLine();
}
}
}
④ 포그라운드 및 백그라운드 스레드
- Foreground 스레드는 메인 쓰레드가 종료되더라도 계속 실행, 실행이 결과가 나올 때까지 기다리는 방식
- Background 스레드는 메인 쓰레드가 종료되면 바로 프로세스를 종료
Thread t6 = new Thread(new ThreadStart(Run));
t6.IsBackground = false; // 기본값 = foreground, 종료되지 않는다.
using System;
using System.Threading;
namespace ServerCore
{
public class Program
{
static void Run()
{
//출력 메시지를 스레드로 무한루프 돌린다고 가정
while (true) Console.WriteLine("Create Thread");
}
static void Main(string[] args)
{
Thread thread = new Thread(Run);
// false = foreground
// true = background
// false 로 설정하면, main 종료와 상관없이 계속 실행된다.
thread.IsBackground = true;
thread.Start();
Console.WriteLine("Main 종료");
}
}
}
스레드 상태
스레드 상태는 디버깅 시나리오 에서만 사용한다.
코드에서 스레드 상태를 사용하여 스레드 활동을 동기화하면 안된다.
학습 단계에서 스레드 상태 값은, 로직의 구현보다 스레드의 동작과 스레드 실행의 흐름을 이해하기 위해 짚고 넘어간다.
스레드 종료
- 스레드를 Running 상태이더라도 강제로 중단시킨다.
- 스레드는 자원을 공유한다. 강제 종료시 어떤 문제가 발생할지 알 수 없다.
- 스레드가 WaitSleepJoin 상태일 때
- 즉시 ThreadInterruptedException 발생
- catch 절에서 예외를 받아서 쓰레드를 종료하면 된다.
- 스레드가 WaitSleepJoin 상태가 아닐 때 (e.g Running 상태)
- ThreadInterruptedException을 예약
- WaitSleepJoin 상태로 Block 되면 ThreadInterruptedException 발생
- catch 절에서 예외를 받아서 쓰레드를 종료하면 된다.
- 사용
- "절대로 중단되면 안 되는 작업"
- "현재 처리중인 작업을 완료하고 나서 스레드를 종료"
스레드 정지
- 지정한 시간만큼 정지한다.
- 즉시 중단
- suspend
- 호출될때까지 정지한다.
- 다른 스레드도 정지 시킬 수 있다.
- 중단되지 않을 수 있다.