2012-01-30 17 views
2

メインスレッドには、自分のWPF GUIと、時にはメインスレッドで非同期にコードを実行する必要がある1つ以上のバックグラウンドスレッド(たとえば、GUIのステータス更新)が含まれています。WPF GUIスレッドでコードを実行するための好ましい方法は何ですか?

あり、この達成するために二つの方法(私が知っている、多分それ以上)のとおりです。使用してデリゲートを呼び出すことによって、ターゲットスレッドの同期コンテキストからTaskSchedulerを使用してタスクをスケジュールすることによって

  1. は、と
  2. ターゲットスレッドからのDispatcherコードで

using System.Threading.Tasks; 
using System.Threading; 

Action mainAction =() => MessageBox.Show(string.Format("Hello from thread {0}", Thread.CurrentThread.ManagedThreadId)); 
Action backgroundAction; 

// Execute in main thread directly (for verifiying the thread ID) 
mainAction(); 

// Execute in main thread via TaskScheduler 
var taskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 
backgroundAction =() => Task.Factory.StartNew(mainAction, CancellationToken.None, TaskCreationOptions.None, taskScheduler); 
Task.Factory.StartNew(backgroundAction); 

// Execute in main thread via Dispatcher 
var dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher; 
backgroundAction =() => dispatcher.BeginInvoke(mainAction); 
Task.Factory.StartNew(backgroundAction); 

例えば、私はすでにTPLをたくさん使うので、私は、TPLベースのバージョンが好きで、それは多くの柔軟性を提供してくれるタスクを待つか、他のタスクと連鎖させるしかし、私が今まで見たWPFコードのほとんどの例では、Dispatcherバリアントが使用されていました。

私はその柔軟性を必要とせず、ターゲットスレッドでいくつかのコードを実行したかったと仮定すると、一方の方法よりも一方の方法を好む理由は何ですか?パフォーマンスに何か影響はありますか?

+1

+1。私は 'FromCurrentSynchronizationContext()'メソッドが存在することさえ知りませんでした! – Cameron

+1

ワンタイム/ワンオフ非同期処理に 'Tasks'を使うことを考えます。しかしながら、 'Dispatcher.Invoke'を使用することは、アプリケーションの長さを実行し、周期的に情報を取得するバックグラウンドスレッドに必要と思われます。 –

答えて

9

を私は非常にあなたがTask-based Asynchronous Pattern文書を読むことをお勧めします。これにより、asyncawaitが通りに当たったときにAPIを構築できるようになります。

あなたのソリューション(blog post)と同様に、TaskSchedulerを使用して更新プログラムをキューに入れましたが、私はその方法を推奨しません。

TAP文書は、よりエレガントに問題を解決し、簡単なソリューションを持っている:バックグラウンド操作が進捗レポートを発行したい場合、それはタイプIProgress<T>の引数を取る:

public interface IProgress<in T> { void Report(T value); } 

それを提供するために、その後は比較的簡単です基本的な実装:

public sealed class EventProgress<T> : IProgress<T> 
{ 
    private readonly SynchronizationContext syncContext; 

    public EventProgress() 
    { 
    this.syncContext = SynchronizationContext.Current ?? new SynchronizationContext(); 
    } 

    public event Action<T> Progress; 

    void IProgress<T>.Report(T value) 
    { 
    this.syncContext.Post(_ => 
    { 
     if (this.Progress != null) 
     this.Progress(value); 
    }, null); 
    } 
} 

SynchronizationContext.Currentは、実際のTask秒を必要とせずに、本質的にTaskScheduler.FromCurrentSynchronizationContextです)。

非同期CTPには、上記のEventProgress<T>と類似していますが、よりパフォーマンスの高いタイプのIProgress<T>Progress<T>が含まれています。 CTPレベルのものをインストールしたくない場合は、上記のタイプを使用できます。要約する

、4つのオプションは本当にあります

  1. IProgress<T> - これは将来的に非同期コードが書き込まれる方法です。また、バックグラウンド操作ロジックと、ユーザーのUI/ViewModelの更新コードを区別する必要があります。これは良いことです。
  2. TaskScheduler - 悪いアプローチではありません。それは私が長い時間の間、IProgress<T>に切り替える前に使ったものです。ただし、UI/ViewModelの更新コードをバックグラウンド操作ロジックから強制的に削除することはありません。
  3. SynchronizationContext- a lesser-known APIを介してTaskSchedulerと同じ長所と短所。
  4. Dispatcher - 実際にはではないをお勧めします。 ViewModelを更新するバックグラウンド操作について考えてみましょう。つまり、進行状況の更新コードにUI固有のものはありません。この場合、Dispatcherを使用してViewModelをUIプラットフォームに結びつけました。不快な。

P.非同期CTPを使用する場合は、SynchronizationContext経由でUIスレッドに切り替えた後にINotifyPropertyChangedで進捗レポートを送信するもの(PropertyProgress)を含め、Nito.AsyncEx libraryにいくつかの追加の実装があります。

+0

+1を感謝したいと思います。本当に役に立つ情報がたくさんあります。出荷する非同期を待つことはできません。 – PersonalNexus

+0

現時点では、本番環境でAsync CTPを使用することは可能です。複雑な 'await'式(' var result = await DoMyStuffAsync(); 'のような文を使用する)を使用しない限り、非常に安定しています。通常、私は非常に保守的で、CTPレベルのものは使用しませんが、Async CTPはうまく焼き付けられています。 –

+0

提供されたコードはスレッドセーフではありません - EventProgress に競合状態があります。 「新しいSynchronizationContext」のために、IProgress .Reportメソッドは、オブジェクトのコンシューマとは異なるスレッドで呼び出すことができます。コンシューマは、イベントが発生するのと同時にハンドラをProgressイベントにアタッチまたはデタッチすることができます。次のコード行がNullReferenceExceptionをスローする間、 "if(this.Progress!= null)"が成功する可能性があります。しかし、TAPパターンについての素晴らしい提案は、完全にプライベート/カプセル化された非同期コードに対しても役に立ちます。 – ShadowChaser

1

小さなUI操作では通常Dispatcherを使用しますが、多くの処理が重い場合は、タスクのみを使用します。私はまた、UI関連ではないすべてのバックグラウンドタスクにTPLを使用します。

あなたはUIで作業を行う場合に便利である、異なるDispatcher Prioritiesでタスクを実行するためにDispatcherを使用することができ、しかし私は、バックグラウンドタスクに関与重い処理がたくさんある場合、それはまだUIスレッドをロックアップ見つけます私は大規模な作業には使用しません。

-1

すべての重い操作に基づいてタスクを使用しますが、GUIを更新する必要がある場合はディスパッチャーを使用してください。そうしないと、クロススレッド例外が発生します。私の知る限り、あなたがGUI

更新更新するために、ディスパッチャを使用する必要が承知しているよう:ピート・ブラウンは、彼のブログでも、それをカバーしていhttp://10rem.net/blog/2010/04/23/essential-silverlight-and-wpf-skills-the-ui-thread-dispatchers-background-workers-and-async-network-programming

+0

TPLを使用してWPFスレッドで何かを実行することはできませんか? [このMicrosoftのサンプル](http://msdn.microsoft.com/en-us/library/dd997394.aspx)(これは私の質問を促しました)は、そうでないことを示唆しています。 – PersonalNexus

+0

申し訳ありませんが、msdnページを正しく見ると、その方法の仕組みが強調されます。タスクを開始するコストは、ディスパッチャを使用して迅速に更新を行うことができます。私はTAP文書へのリンクのために2つの方法の性能比較 – MJJames

関連する問題