본문 바로가기
Program/C# .NET

[C# WPF] Thread Safe한 사용법 / Abort Exception / EventWaitHandle로 Join하기

by 냠만 2025. 2. 12.

[개요]

개발을 진행하다 보면 필수적으로 사용하게 되는 테크닉이 Thread의 개념입니다. 요새는 Task로 더 편하게 사용할 수 있지만 Task 내부는 Thread로 구성되어 있으며 실질적으로 사용되는 위치에 따라서 Thread와 Task를 구분하여 사용할 줄 알아야 합니다. 좋은 기능이 있어도 장단점을 알고 적절한 곳에 사용할 수 있느냐도 개발자의 급을 나눌 수 있다고 생각하거든요.

오늘은 Thread의 대강 기념과 어떻게 하면 안전하게 사용할 수 있고 어떤 방식으로 Thread를 기다리는지 알아보겠습니다.

 

[Thread 정의]

Thread의 정의는 프로세스 내에서 실제로 작업을 수행하는 주체를 의미합니다. 뭐든 정의는 어렵지만 중요하기도 하지요. 정의가 어렵다면 요새 컴퓨터가 발전하면서 cpu가 점차 세분화되고 있는데 CPU >> CORE >> THREAD라고 생각하시면 됩니다.

CPU : 논리적 사고를 총괄하는 주체(집안일회사)
CORE : 논리적 사고를 바탕으로 업무를 진행하는 일꾼(파트타임 직원)
THREAD : 업무를 진행하기 위해 사용되는 도구 (세탁기, 로봇청소기, 냉장고 등)

 

집안일이라는 프로세스를 진행시키기 위해서 집안일회사에서 파트타임직원을 파견해서 파트타임 직원이 업무를 효율적으로 진행하기 위해 여러 전자도구를 사용한다고 보시면 됩니다.

로봇청소기에게 청소해 하고있으면 직원은 다른 일을 해도 청소기는 돌아가죠. 청소기가 다 돌았으면 알림을 받으면 아 청소기가 끝났구나 하고 먼지통을 비워주면 됩니다.

 

즉 프로그램을 코딩할 때 연산이 오래걸리는 잡무를 여러 Thread에 분산시켜서 병렬처리를 하게 되면 속도가 더 빠르게 되는 원리입니다.

 

[Thread 선언과 시작]

Thread를 선언하고 시작하는 방법은 간단합니다. new 키워드로 선언하고 시작해주면 됩니다.

Do_Work는 함수로 매개변수는 존재하지 않아도 됩니다. 그냥 선언만 되어있으면 되고 Thread Start 이후에 1회 실행되는 함수입니다. Thread는 도구 개념으로 사용할 때 새로 선언해서 명령을 내려줘야 사용이 가능하다는 점 참고하시면 됩니다.

 

[Thread Abort 종료]

 

Thread를 종료하는 방법은 초기에는 Abort를 사용하고 객체를 null로 변경해주곤 했습니다. 이렇게 진행하면 중간에 어느 지점에서 Thread가 종료되는지 알 수 없을뿐더러 사용 중인 변수가 있었다면 크리티컬섹션으로 묶여서 다른 Thread에서 접근이 불가능해져 프로그램이 멈추는 현상이 발생될 수 있습니다.

이런 문제점 때문에 현재는 .NET 5부터 Abort는 사용이 금지되어 있으며 다른 방법으로 Thread를 종료해야 합니다.

Thread를 생성하고 실행한 다음 강제로 종료하려 할 경우 System.PlatformNotSupportedException이 발생됩니다. Thread abort is not supported on this platform. 메시지는 제가 테스트로 개발한 프로그램이. NET8이기 때문에 발생되는 문제입니다.

. NET5 아래이거나. NET Framework를 사용하시는 분들은 적용이 되실 수 있는데 절대 사용하면 안 되는 명령어입니다.

[Thread 안전하게 종료하는 방법]

Thread를 안전하게 종료하는 방법 중 가장 좋은 방법은 플래그(Flag)를 사용하는 방법입니다. Flag는 해당 Thread에 Bool변수를 하나 선언해서 루프를 강제적으로 탈출할 수 있도록 해주는 방법입니다.

Thread를 선언할 때 IsBackground 옵션을 설정하는 방법도 좋은 방법이지만 추후에 포스팅 할 Running 상태를 확인하기 위해서는 해당 방식보단 Flag방식을 이용하는 걸 추천드립니다.

 

아래 프로그램으로 추가 설명을 해봅시다.

프로그램은 간단합니다.

Thread A : 1 ~ 100까지 차례대로 출력
Thread B : 100 ~ 200까지 차례대로 출력

Create Button : Thread A, B 생성 및 시작
Abort Thread A : Thread A 종료
Abort Thread B : Thread B 종료

 

프로그램을 구성하는 변수입니다. EventWaitHandle이라는 친구를 사용하는데 Join은 Threadpool에서 사용할 수 없지만 Event는 사용할 수 있기에 Join은 몰라도 EventWaitHandle은 알아야 합니다.

객체 선언 시 뒤에 변수가 두 가지가 들어갑니다. 첫 번째 칸(true)은 Initial 상태를 의미하며 제어권을 돌려줄지 안 돌려줄지를 의미합니다. 제어권이란 프로그램을 해당 스레드가 끝날 때까지 기다릴 것이냐, 다른 일을 진행할 것이냐를 선택하는 것으로 동기 비동기 개념으로 이해하셔도 됩니다. true는 비동기를 의미하며 해당 스레드가 일할동안 안 기다리겠다입니다.

 

EventResetMode는 객체를 수동으로 리셋할 건지 자동으로 리셋할 건지를 선택하는데 자동으로 리셋하는 경우 한 개의 스레드만 생성되고 동기상태로 바뀌어 버립니다. 가능하면 ManualReset으로 사용하고 커스터마이징 하시는 걸 추천드립니다.

 

Thread를 생성하고 각각 Thread를 종료시키기 위한 Flag 세팅을 진행합니다.

 

Thread의 실질적인 일하는 장소입니다. 전달받은 매개변수는 무시하셔도 되고 단순하게 1 ~ 200까지 화면에 순차적으로 출력하는 업무를 진행합니다.

Thread A는 별 특징 없이 Flag가 false로 변경되면 Thread가 종료되고 Thread B는 Flag가 false로 종료되면 EventWaitHandle에 나 종료된다라는 신호를 Set 해줍니다.

 

프로그램은 큰 구성은 없으니 소스만 확인하셔도 충분히 따라 하실 수 있을 겁니다. 다만 주의할 점은 Flag를 사용하여 종료 시 본인이 원하는 시점에서 정상적으로 종료되는지는 추가적으로 확인이 어렵다는 점을 참고해야 합니다. 실제적으로 위 예제에서도 ThreadB 종료 Flag가 Set 되었는데 1회 더 출력된 걸 확인할 수 있습니다. Thread를 사용한 프로그래밍 할 때 반드시 점검되어야 하는 부분이며 사용에 항상 주의가 필요하겠습니다.