성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - WPF의 Dispatcher Queue 동작 확인</h1> <p> WPF의 경우, 재미있게도 Dispatcher Queue에 이벤트를 걸 수 있는 기능을 제공합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // .NET Framework 4.8 WPF 프로젝트 public MainWindow() { InitializeComponent(); <span style='color: blue; font-weight: bold'>Dispatcher.Hooks.OperationStarted += Hooks_OperationStarted; Dispatcher.Hooks.OperationCompleted += Hooks_OperationCompleted; Dispatcher.Hooks.OperationPosted += Hooks_OperationPosted;</span> } // Dispatcher 작업이 수행된 경우 알림 private void Hooks_OperationCompleted(object sender, DispatcherHookEventArgs e) { } // Dispatcher 작업이 시작하는 시점에 알림 private void Hooks_OperationStarted(object sender, DispatcherHookEventArgs e) { } // Dispatcher에 작업이 추가된 경우 알림 private void Hooks_OperationPosted(object sender, System.Windows.Threading.DispatcherHookEventArgs e) { } </pre> <br /> 아마도 대부분의 경우, Dispatcher는 UI 스레드와 연동하는 용도로 사용할 텐데요, 실제로 예를 하나 들어볼까요? ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private async void Window_Loaded(object sender, RoutedEventArgs e) { new Thread(() => { this.textBox1.Text = "Hello World"; // 2차 스레드에서 UI 요소 접근으로 System.InvalidOperationException 예외 발생 }).Start(); } </pre> <br /> 위의 코드는 다들 예상하시는 것처럼, <a target='tab' href='https://www.sysnet.pe.kr/2/0/11561'>UI 요소를 생성하지 않은 스레드로부터 접근한 탓에</a> "System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'" 예외가 발생합니다.<br /> <br /> WPF의 경우 위의 문제를 Dispatcher에 전달하는 방식으로 해결하는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > new Thread(async () => { // 아래의 3가지 방식은 모두 동일한 Dispatcher를 접근 // Application.Current.Dispatcher.Invoke(() => // Dispatcher.Invoke(() => this.textBox1.Dispatcher.Invoke(() => // 동기 방식으로 UI에 전달 { this.textBox1.Text = "Hello World1"; }); // this.textBox1.Dispatcher.<a target='tab' href='https://www.sysnet.pe.kr/2/0/11211'>BeginInvoke</a>(() => // 비동기 방식으로 UI에 전달 // { // this.textBox1.Text = "Hello World2"; // }); // await this.textBox1.Dispatcher.InvokeAsync(() => // await 비동기 방식으로 UI에 전달 // { // this.textBox1.Text = "Hello World3"; // }); }).Start(); </pre> <br /> 대표적으로 Invoke가 된 경우, Dispatcher에 전달되었는지를 Hooks_OperationPosted 이벤트를 통해 다음과 같은 식으로 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private void <span style='color: blue; font-weight: bold'>Hooks_OperationPosted</span>(object sender, System.Windows.Threading.DispatcherHookEventArgs e) { <span style='color: blue; font-weight: bold'>string name = GetName(e.Operation);</span> Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] {name}"); } static string GetName(DispatcherOperation dop) { PropertyInfo pi = typeof(DispatcherOperation).GetProperty("Name", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); return pi.GetValue(dop) as string; } </pre> <br /> 단지, Dispatcher를 우리만 쓰는 것은 아니므로 저렇게 하면 너무 많은 출력 결과가 나와 구분이 잘 안되는데요, 이를 위해 다음과 같은 코드를 곁들인 후,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > new Thread(() =>) { System.Diagnostics.Trace.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}]==============================================="); Application.Current.Dispatcher.Invoke(() => { this.textBox1.Text = "Hello World1"; }); System.Diagnostics.Trace.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}]==============================================="); } </pre> <br /> F5 디버깅으로 실행하면 Output 창에 다음과 같은 출력을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ...[생략]... [1] System.Windows.Window.<PostContentRendered>b__204_0 <span style='color: blue; font-weight: bold'>[9]</span>=============================================== <span style='color: blue; font-weight: bold'>[9] WpfApp1.MainWindow.<Window_Loaded>b__10_1</span> [1] System.Windows.Media.MediaContext.RenderMessageHandler <span style='color: blue; font-weight: bold'>[9]</span>=============================================== [1] System.Windows.Documents.TextSelection.UpdateCaretStateWorker ...[생략]... </pre> <br /> (Invoke에 전달한 익명 함수가 C# 컴파일러에 의해 분리돼 "WpfApp1.MainWindow.<Window_Loaded>b__10_1"라는 이름으로 컴파일이 돼 "====" 식별자를 두지 않았다면 아마 찾기 힘들 것입니다. 게다가, 저 이름은 상황에 따라 컴파일 시 바뀔 수 있습니다.)<br /> <br /> 자... 결과를 보면, "===" 식별자를 출력한 스레드 ID가 9번이고, 이와 함께 Hooks_OperationPosted도 9번 스레드에서 실행이 됐습니다. 따라서, 저 이벤트 핸들러는 Dispatcher에 항목을 Enqueue하는 주체를 기준으로 실행이 되는 것을 알 수 있습니다. 이후, 저렇게 Dispatcher에 추가된 항목은 UI 스레드가 여유가 될 때 직렬로 실행해 줍니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 재미 삼아, Hooks_OperationPosted에 전달된 DispatcherHookEventArgs 인자를 이용해 우리가 직접 Invoke를 해볼까요? ^^<br /> <br /> DispatcherHookEventArgs 타입의 e 인스턴스를 Visual Studio의 Watch 창으로 조사하다 보면 _method 필드가 왠지 그 역할을 하는 듯합니다. (그냥 감으로 잡아내야 합니다. ^^; 혹은 WPF 소스코드를 분석하던가!)<br /> <br /> 따라서, 다음과 같이 코딩을 추가해 주면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private void Hooks_OperationPosted(object sender, System.Windows.Threading.DispatcherHookEventArgs e) { string name = GetName(e.Operation); Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] {name}"); string target = "WpfApp1.MainWindow.<Window_Loaded>b__10_1"; if (name == target) { <span style='color: blue; font-weight: bold'>System.Delegate method = GetMethod(e.Operation);</span> <span style='color: blue; font-weight: bold'>if (method is Action action) { action(); }</span> } } static System.Delegate GetMethod(DispatcherOperation dop) { FieldInfo fi = typeof(DispatcherOperation).GetField("_method", BindingFlags.Instance | BindingFlags.NonPublic); return fi.GetValue(dop) as System.Delegate; } </pre> <br /> 실행 시, "this.textBox1.Text = "Hello World1";" 코드에서 "System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'" 예외가 발생할 것입니다. 왜냐하면, UI 스레드가 아닌 9번 스레드에서 실행했으므로 이전과 동일한 오류가 발생한 것입니다.<br /> <br /> 그렇긴 해도, 어쨌든 우리가 원하는 결과는 얻었습니다. ^^ 테스트를 위해 UI를 접근하지 않는 Invoke 코드로 바꾸면 예외 없이 실행하는 것을 볼 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 실제로 저런 코드를 작성해 현업 프로그램에 사용할 것은 아니므로 쓸데없는 설명이 될 수 있지만, 마지막으로 하나 더 언급할 것이 있습니다.<br /> <a name='run_twice'></a> <br /> 위의 Hooks_OperationPosted에서 action() 호출을 해도, Dispatcher Queue에 아직 그 작업 항목은 남아 있기 때문에 이후 UI 스레드에 의해 중복 실행됩니다. 여기서 재미있는 건, Hooks_OperationPosted 이벤트가 호출되는 것은 (이전에도 언급했듯이) 호출 측 스레드에 의해 Dispatcher Queue에 작업을 추가하는 순간 발생한다는 점입니다. 따라서, 만약 이 순간 UI 스레드가 놀고 있다면 곧바로 깨어나 Hooks_OperationPosted에 알림 중인 작업을 시작할 수 있습니다.<br /> <br /> 따라서, Hooks_OperationPosted에서의 action() 호출과 UI 스레드가 _method 작업을 실행하는 순서는 엎치락뒤치락 할 수 있습니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=2142&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1149
(왼쪽의 숫자를 입력해야 합니다.)