본 글은 https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/threading-model?view=netframeworkdesktop-4.8&viewFallbackFrom=netdesktop-8.0 에 나온 WPF의 Thread에 관해서 정리한 글입니다.
개요
WPF는 개발자의 편의를 위해 Thread 를 직접 사용하는 수고를 덜어준다. 사실은 거의 사용하지 않아도 된다! 그래서 WPF개발자들은 여러 개의 Thread 를 사용해야 하는 복잡함을 잘 겪지 않지만, 결국에는 하나의 Thread로는 한계가 발생할 수 있다.
이 글에서는, 비동기 호출을 위해서 'InvokeAsync' 매써드를 다룬다. 'InvokeAsync' 매써드는 'Action' 과 'Func<TResult>'를 매개변수로 받으며, 'Task'를 Property로 갖는, 'DispatcherOperation' 또는 'DispatcherOperation<TResult>'를 반환한다.
(자세한 사항은 위 링크를 참조)
WPF 앱은 2 개의 Thread 로 시작한다. 하나는 랜더링을 위해서, 다른 하나는 UI를 관리한다. 랜더링 Thread가 막히지 않아야 사용자는 프로그램과 상호작용하기 더 편하다. 랜더링 Thread는 보통 백그라운드에 숨어 있고, UI Thread는 입력을 받고, 이벤트를 처리하고, 화면에 그리고, 코드를 실행한다.
UI Thread는 'Dispatcher'에 해야 할 작업들을 queue 데이터 구조에 넣어놓는다. (Queue : First-In First-Out). 'Dispatcher' 큐에 들어간 작업들은 차례대로 수행된다. 이 때, 들어간 작업이 너무 크다면? UI Thread가 멈추게 되며, 사용자는 화면이 멈췄다고 생각한다. 이런 현상은 개발자로서 당연히 피해야 한다.
너무 큰 작업들을 다른 Thread에서 실행하려면, 새로운 Thread를 만들어야 한다. 하지만 전통적으로 Windows 운영체제는 UI 작업을 오직 하나의 Thread만 관리하게 만들어 놨다. (VerifyAccess 매써드, 자세한 사항은 구글링). 따라서, 새로운 Thread는 UI 작업을 직접 수행하지 않으며 간접적으로 UI Thread에 작업을 수행하게 맡겨야 한다. 이 과정이 위에서 언급한 'Dispatcher.InvokeAsync' , 'Dispatcher.BeginInvoke', 'Dispatcher.Invoke'로 이루어진다.
'InvokeAsync' : 비동기, 즉시 컨트롤이 UI Thread에게 반환된다
'BeginInvoke' : 비동기, 즉시 컨트롤이 UI Thread에게 반환된다
'Invoke' : 동기, 작업이 끝나기 전까지 UI Thread는 정지 상태이다
Single-Threaded 앱
WPF 는 내부적으로 사용자의 입력 (마우스, 키보드) 이 UI Thread에서 실행되고 있는 작업을 방해하게 하지 않는다. 따라서, WPF 개발자는 'Dispatcher'에 오래 실행되는 작업을 할당해서는 안 되며, 'Dispatcher'가 사용자의 입력을 받을 수 있게 해야 한다.
따라서, 오래 걸리는 작업은 'Dispatcher'에 'InvokeAsync' 비동기 매써드를 사용하는 게 좋다. 참고로, 마이크로소프트 홈페이지 설명에는 'InvokeAsync'는 작업 수행에 있어서 'delegate' (한국말로 위임)을 사용한다고 나와있다. 간단하게 설명하면, 'InvokeAsync' 함수에 우리가 사용할 함수를 매개 변수로 넘겨주면 된다!
여러 개의 Windows, 여러 개의 Threads
위에서 설명한 방법은 UI Thread에 작업을 넘기는 방식이다. 하지만 여러 개의 Window를 개별 Thread로 조절하고 싶다면 어떻게 해야 할까? 새로운 Thread를 만들면 된다.
예시 코드:
Thread newWindowThread = new Thread(ThreadStartingPoint);
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
newWindowThread.Start();
첫번째 줄에서, 'Thread' 에 'ThreadStartingPoint' 함수를 넘겨준다. 이해를 돕기 위해 'ThreadStartingPoint' 함수 코드를 첨부한다.
private void ThreadStartingPoint()
{
new MultiWindow().Show();
System.Windows.Threading.Dispatcher.Run();
}
Task.Run 으로 오래 걸리는 작업 실행하기
여러 개의 Window에 개별 Thread를 부여한다면, 'InvokeAsync'를 사용한다고 하더라도 불편한 상황이 발생할 수 있다. 이 때 사용 가능한게 'Task.Run' 매써드이다.
비동기, 동기로 사용할 지는 사용자에게 달렸으며 'async', 'await'을 사용하면 된다. 자세한 사항은 C# 문법을 참조!
'Computer Science > C#' 카테고리의 다른 글
WPF 클래스 (class) 개요 (0) | 2024.04.30 |
---|---|
C# Net Framework 역사 및 개념 정리 (C#, DotNet 구조 파악) (0) | 2023.12.17 |