Dispatcher.Invokeを使用して、非メインスレッドからWPFコントロールを変更します


81

最近、WPFでプログラミングを開始し、次の問題に遭遇しました。使い方がわかりませんDispatcher.Invoke()。私はスレッド化の経験があり、使用したばかりのいくつかの簡単なWindowsフォームプログラムを作成しました。

Control.CheckForIllegalCrossThreadCalls = false;

はい、私はそれがかなり不十分であることを知っていますが、これらは単純な監視アプリケーションでした。

実際には、バックグラウンドでデータを取得するWPFアプリケーションを作成しています。新しいスレッドを開始して、(Webサーバーから)データを取得するための呼び出しを行います。次に、WPFフォームに表示します。問題は、このスレッドからコントロールを設定できないということです。ラベルも何もありません。これはどのように解決できますか?

コメントへの回答:
@Jalfp:
データを取得するときに、「新しいトレッド」でこのDispatcherメソッドを使用しますか?または、バックグラウンドワーカーにデータを取得させ、フィールドに配置して、このフィールドが入力されるまで待機する新しいスレッドを開始し、ディスパッチャーを呼び出して、取得したデータをコントロールに表示する必要がありますか?


そのCheckForIllegalCrossThreadCallsは素晴らしいです。迅速な「誰が気にする」アプリケーションについては、以前に知って
いればよかった

回答:


177

まず、Dispatcherは長いブロッキング操作(Webサーバーからのデータの取得など)を実行するようには設計されていないことを理解する必要があります。UIスレッドで実行される操作(プログレスバーの値の更新など)を実行する場合は、ディスパッチャーを使用できます。

できることは、バックグラウンドワーカーでデータを取得し、ReportProgressメソッドを使用してUIスレッドで変更を伝達することです。

本当にディスパッチャを直接使用する必要がある場合、それは非常に簡単です。

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

22
'new Action('の部分を取り除き、ラムダ式を使用するだけです:DispatcherPriority.Background、()=> this.progressBar.Value = 50
jrista 2009年

ええ、なぜ私がここにアクションを置いたのかわかりません:p
japf 2009年

1
@Carstenこの回答は、System.Windows.Applicationクラスを使用するWPFアプリケーション用です。
joshuapoehls 2013

10
@jrista:本当にできますか?なしで試してみるとCS1660表示されnew Action(...)ます。
またはマッパー2013

6
@jrista:一般的には正しいですが、この記事では、渡されたメソッドなどのパラメータのないメソッドの場合に機能しない理由を説明しBeginInvoke、代わりにコンパイラエラーCS1660が生成されます。
またはマッパー2013

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から非表示になっているため、インテリセンスが表示されることを期待せずに記述してください。CheckAccessにはパフォーマンスのオーバーヘッドがあることに注意してください(数ナノ秒のオーバーヘッド)。どんな犠牲を払っても「呼び出し」を実行するために必要なマイクロ秒を節約したい場合にのみ、より良いです。また、メソッドを呼び出すときにUIスレッド内にあるかどうかが確実な場合は、常に2つのメソッド(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)));

メソッド名は、現在のコンテキスト内に存在しない
AriesConnolly

0

上記の@japfの回答は正常に機能しており、私の場合、CEFブラウザーがページの読み込みを完了したら、マウスカーソルをスピニングホイールから通常の矢印に戻したいと思いました。それが誰かを助けることができる場合のために、ここにコードがあります:

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.