Dispatcher.Invoke를 사용하여 주 스레드가 아닌 스레드에서 WPF 컨트롤 변경


81

나는 최근에 WPF에서 프로그래밍을 시작했고 다음과 같은 문제에 부딪 혔습니다. 방법을 사용하는 Dispatcher.Invoke()방법 을 이해하지 못합니다 . 스레딩 경험이 있으며 방금 사용한 몇 가지 간단한 Windows Forms 프로그램을 만들었습니다.

Control.CheckForIllegalCrossThreadCalls = false;

예, 그것은 꽤 절름발이는 것을 알고 있지만 이것은 단순한 모니터링 응용 프로그램이었습니다.

사실은 이제 백그라운드에서 데이터를 검색하는 WPF 응용 프로그램을 만들고 있습니다. 새 스레드를 시작하여 웹 서버에서 데이터를 검색하는 호출을 시작합니다. 이제 WPF 양식에 표시하고 싶습니다. 문제는이 스레드에서 어떤 제어도 설정할 수 없다는 것입니다. 레이블이나 그 어떤 것도 아닙니다. 이 문제를 어떻게 해결할 수 있습니까?

답변 코멘트 :
@Jalfp :
그래서 데이터를 얻을 때 '새 트레드'에서이 Dispatcher 메서드를 사용합니까? 아니면 백그라운드 작업자가 데이터를 검색하고 필드에 넣은 다음이 필드가 채워질 때까지 대기하고 디스패처를 호출하여 검색된 데이터를 컨트롤에 표시하는 새 스레드를 시작해야합니까?


CheckForIllegalCrossThreadCalls는 굉장합니다. 빠른 "누가 신경 쓰는"응용 프로그램에 대해 이전에 알았 으면 좋겠어요
Gaspa79

답변:


177

첫 번째는 Dispatcher가 긴 차단 작업 (예 : WebServer에서 데이터 검색 ...)을 실행하도록 설계되지 않았 음을 이해하는 것입니다. UI 스레드에서 실행될 작업 (예 : 진행률 표시 줄의 값 업데이트)을 실행하려는 경우 Dispatcher를 사용할 수 있습니다.

할 수있는 일은 백그라운드 작업자에서 데이터를 검색하고 ReportProgress 메서드를 사용하여 UI 스레드의 변경 사항을 전파하는 것입니다.

Dispatcher를 직접 사용해야하는 경우 매우 간단합니다.

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Background,
  new Action(() => this.progressBar.Value = 50));

22
'new Action ('부분을 제거하고 간단히 람다 식을 사용할 수 있습니다. DispatcherPriority.Background, () => this.progressBar.Value = 50
jrista

그래 내가 왜 여기에 액션을 넣었는지 모르겠어 : p
japf

1
@Carsten이 답변은 System.Windows.Application 클래스를 사용하는 WPF 응용 프로그램을위한 것입니다.
joshuapoehls 2013

10
@jrista : 정말 수 있습니까? 나는군요 CS1660을 하지 않고 시도 할 때 new Action(...).
OR Mapper

6
@jrista : 일반적으로 사실입니다. 이 기사에서는 전달 된 것과 같은 매개 변수없는 메서드의 경우 작동하지 않는 이유를 설명 BeginInvoke하고 대신 컴파일러 오류 CS1660이 발생합니다.
OR Mapper

31

japf가 올바르게 대답했습니다. 여러 줄로 된 동작을보고있는 경우를 대비하여 아래와 같이 작성할 수 있습니다.

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Background,
  new Action(() => { 
    this.progressBar.Value = 50;
  }));

성능에 대해 알고 싶은 다른 사용자를위한 정보 :

고성능을 위해 코드를 작성해야하는 경우 먼저 CheckAccess 플래그를 사용하여 호출이 필요한지 확인할 수 있습니다.

if(Application.Current.Dispatcher.CheckAccess())
{
    this.progressBar.Value = 50;
}
else
{
    Application.Current.Dispatcher.BeginInvoke(
      DispatcherPriority.Background,
      new Action(() => { 
        this.progressBar.Value = 50;
      }));
}

CheckAccess () 메서드는 Visual Studio 2015에서 숨겨져 있으므로 intellisense가 표시 될 것으로 예상하지 않고 작성하면됩니다. CheckAccess에는 성능에 대한 오버 헤드가 있습니다 (수 나노초에 오버 헤드). 어떤 비용 으로든 '호출'을 수행하는 데 필요한 마이크로 초를 절약하려는 경우에만 더 좋습니다. 또한 메서드를 호출 할 때 UI 스레드에 있는지 여부가 확실 할 때 항상 두 개의 메서드 (Invoke 사용시 및없는 경우)를 만드는 옵션이 있습니다. 디스패처의 이러한 측면을 살펴 봐야하는 경우는 드문 경우입니다.


4

스레드가 실행 중이고 현재 스레드에 의해 차단 된 메인 UI 스레드를 실행하려면 다음을 사용하십시오.

현재 스레드 :

Dispatcher.CurrentDispatcher.Invoke(MethodName,
    new object[] { parameter1, parameter2 }); // if passing 2 parameters to method.

기본 UI 스레드 :

Application.Current.Dispatcher.BeginInvoke(
    DispatcherPriority.Background, new Action(() => MethodName(parameter)));

MethodName이 현재 컨텍스트에 존재하지 않습니다
AriesConnolly

0

위의 @japf 대답은 잘 작동하며 제 경우 에는 CEF 브라우저 가 페이지로드를 마치면 마우스 커서를 Spinning Wheel 에서 일반 화살표로 다시 변경하고 싶었 습니다. 누군가에게 도움이 될 수있는 경우 코드는 다음과 같습니다.

private void Browser_LoadingStateChanged(object sender, CefSharp.LoadingStateChangedEventArgs e) {
   if (!e.IsLoading) {
      // set the cursor back to arrow
      Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
         new Action(() => Mouse.OverrideCursor = Cursors.Arrow));
   }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.