WPFDispatcher.InvokeおよびDispatcher.BeginInvoke 使用法。
単純なWPFアプリケーションでボタンを押すと呼び出されるような、長時間実行される「作業」コードがあるとします。
longWorkTextBox.Text = "Ready For Work!";
Action workAction = delegate
{
Console.WriteLine("Starting Work Action");
int i = int.MaxValue;
while (i > 0)
i--;
Console.WriteLine("Ending Work Action");
longWorkTextBox.Text = "Work Complete";
};
longWorkTextBox.Dispatcher.BeginInvoke(DispatcherPriority.Background, workAction);
このコードは、workActionの実行中にユーザーインターフェイスをロックします。これは、Dispatcherの呼び出しが常にUIスレッドで実行されるためです。
これを前提として、私のUIとは別のスレッドでworkActionを実行するようにディスパッチャを構成するためのベストプラクティスは何ですか? I BackgroundWorkerをworkActionに追加して、UIがそのようなロック:
longWorkTextBox.Text = "Ready For Work!";
Action workAction = delegate
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate
{
Console.WriteLine("Starting Slow Work");
int i = int.MaxValue;
while (i > 0)
i--;
Console.WriteLine("Ending Work Action");
};
worker.RunWorkerCompleted += delegate
{
longWorkTextBox.Text = "Work Complete";
};
worker.RunWorkerAsync();
};
longWorkTextBox.Dispatcher.BeginInvoke(DispatcherPriority.Background, workAction);
BackgroundWorkerを使用する以外に、これを行うよりエレガントな方法はありますか? BackgroundWorkerは一風変わっているといつも聞いていたので、いくつかの代替案を知りたいです。
正直なところ、 BackgroundWorker
がこれに対する最もエレガントなソリューションだと思います。もっと簡単な方法は考えられません。
私もBackgroundWorkerが好きではありません。簡単な代替案は次のようなものです。
using System;
using System.Threading;
using System.Windows;
namespace Sample
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
longWorkTextBox.Text = "Ready For Work!";
}
private void startButton_Click(object sender, RoutedEventArgs e)
{
new Thread(Work).Start();
}
void Work()
{
longWorkTextBox.Dispatcher.BeginInvoke((Action)(() => { longWorkTextBox.Text = "Working..."; }));
Console.WriteLine("Starting Work Action");
int i = int.MaxValue;
while (i > 0)
i--;
Console.WriteLine("Ending Work Action");
longWorkTextBox.Dispatcher.BeginInvoke((Action)(() => { longWorkTextBox.Text = "Work Complete"; }));
}
}
}
簡単でしょう?
チャーリーの答えはあなたが本当に探しているものです。
ただし、可能であれば、個々の作業単位が小さくなり、UIにそれほど影響を与えないように、作業を分割できるかどうかを確認できます。これにより、Dispatcherを直接使用できます。これの良い例がWPFスレッドページにあります。 https://msdn.Microsoft.com/en-us/library/ms741870%28v=vs.100%29.aspx
その名前が示すように、バックグラウンドで実行されるため、Dispatcherでインスタンス化する必要はありません。さらに、このコードをWP7で実行する場合、BeginInvokeはバックグラウンドパラメータを取得しません。
私の推奨は、次のようにBackgroundWorkerを作成することです。
BackgroundWorker worker = new BackgroundWorker;
次に、イベントのハンドラーを作成します。
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork +=new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted +=new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.ProgressChanged +=new ProgressChangedEventHandler(worker_ProgressChanged);
そして最後にあなたは電話します:
bkwkPlayingLoop.RunWorkerAsync();
DoWork内からDispatcherを使用するのは大きな誘惑ですが、代わりにworker.ReportProgress()を呼び出して、そこからUIを処理します。そうしないと、終了イベントの発生に不整合が生じます。
タスクはバックグラウンドワーカーよりも使いやすく、多くのことを実行し、問題が少なく、ほとんど作成されているため、バックグラウンドワーカーを使用する必要がなくなりました。