私は現在、C#で最初のプログラムを書いていますが、この言語は非常に新しいです(これまではCでしか動作しませんでした)。私は多くの研究をしましたが、すべての答えは一般的すぎて、うまくいかなかったのです。
ここで私の(非常に一般的な)問題:ユーザーが入力したいくつかのテキストボックスから入力を取得し、それを使用して多くの計算を行うWPFアプリケーションがあります。約2〜3分かかるので、進行状況バーとテキストブロックを更新して、現在のステータスを確認したいと思います。また、ユーザーからのUI入力を保存してスレッドに渡す必要があるため、3番目のクラスがあり、これを使用してオブジェクトを作成し、このオブジェクトをバックグラウンドスレッドに渡します。当然、計算を別のスレッドで実行するので、UIはフリーズしませんが、すべての計算メソッドは別のクラスの一部であるため、UIを更新する方法がわかりません。多くの調査の後、最適な方法はバックグラウンドワーカーではなくディスパッチャとTPLを使用することだと思いますが、正直なところ、それらがどのように機能するのかわかりません。私自身の質問。
ここに私のプログラムの非常に単純な構造:
public partial class MainWindow : Window
{
public MainWindow()
{
Initialize Component();
}
private void startCalc(object sender, RoutedEventArgs e)
{
inputValues input = new inputValues();
calcClass calculations = new calcClass();
try
{
input.pota = Convert.ToDouble(aVar.Text);
input.potb = Convert.ToDouble(bVar.Text);
input.potc = Convert.ToDouble(cVar.Text);
input.potd = Convert.ToDouble(dVar.Text);
input.potf = Convert.ToDouble(fVar.Text);
input.potA = Convert.ToDouble(AVar.Text);
input.potB = Convert.ToDouble(BVar.Text);
input.initStart = Convert.ToDouble(initStart.Text);
input.initEnd = Convert.ToDouble(initEnd.Text);
input.inita = Convert.ToDouble(inita.Text);
input.initb = Convert.ToDouble(initb.Text);
input.initc = Convert.ToDouble(initb.Text);
}
catch
{
MessageBox.Show("Some input values are not of the expected Type.", "Wrong Input", MessageBoxButton.OK, MessageBoxImage.Error);
}
Thread calcthread = new Thread(new ParameterizedThreadStart(calculations.testMethod);
calcthread.Start(input);
}
public class inputValues
{
public double pota, potb, potc, potd, potf, potA, potB;
public double initStart, initEnd, inita, initb, initc;
}
public class calcClass
{
public void testmethod(inputValues input)
{
Thread.CurrentThread.Priority = ThreadPriority.Lowest;
int i;
//the input object will be used somehow, but that doesn't matter for my problem
for (i = 0; i < 1000; i++)
{
Thread.Sleep(10);
}
}
}
Testmethod内からUIを更新する方法を誰かが簡単に説明してくれたら、とても感謝しています。私はC#とオブジェクト指向プログラミングに慣れていないので、あまりにも複雑な答えなので、理解できない可能性が高いので、最善を尽くします。
また、誰かが一般的にもっと良いアイデアを持っている場合(おそらく、バックグラウンドワーカーまたはその他の何かを使用している場合)、私はそれを見ることができます。
最初に_Dispatcher.Invoke
_を使用してUIを別のスレッドから変更し、別のクラスから変更する必要があります。イベントを使用できます。
次に、メインクラスでそのイベントに登録し、UIに変更をディスパッチし、UIに通知するときにイベントをスローする計算クラスで次のようにします。
_class MainWindow : Window
{
private void startCalc()
{
//your code
CalcClass calc = new CalcClass();
calc.ProgressUpdate += (s, e) => {
Dispatcher.Invoke((Action)delegate() { /* update UI */ });
};
Thread calcthread = new Thread(new ParameterizedThreadStart(calc.testMethod));
calcthread.Start(input);
}
}
class CalcClass
{
public event EventHandler ProgressUpdate;
public void testMethod(object input)
{
//part 1
if(ProgressUpdate != null)
ProgressUpdate(this, new YourEventArgs(status));
//part 2
}
}
_
更新:
これはまだ頻繁に訪問される質問と回答のようですので、この回答を今のやり方で更新したいです(.NET 4.5で)-これは少し長くなります。いくつかの異なる可能性を示します:
_class MainWindow : Window
{
Task calcTask = null;
void buttonStartCalc_Clicked(object sender, EventArgs e) { StartCalc(); } // #1
async void buttonDoCalc_Clicked(object sender, EventArgs e) // #2
{
await CalcAsync(); // #2
}
void StartCalc()
{
var calc = PrepareCalc();
calcTask = Task.Run(() => calc.TestMethod(input)); // #3
}
Task CalcAsync()
{
var calc = PrepareCalc();
return Task.Run(() => calc.TestMethod(input)); // #4
}
CalcClass PrepareCalc()
{
//your code
var calc = new CalcClass();
calc.ProgressUpdate += (s, e) => Dispatcher.Invoke((Action)delegate()
{
// update UI
});
return calc;
}
}
class CalcClass
{
public event EventHandler<EventArgs<YourStatus>> ProgressUpdate; // #5
public TestMethod(InputValues input)
{
//part 1
ProgressUpdate.Raise(this, status); // #6 - status is of type YourStatus
//part 2
}
}
static class EventExtensions
{
public static void Raise<T>(this EventHandler<EventArgs<T>> theEvent,
object sender, T args)
{
if (theEvent != null)
theEvent(sender, new EventArgs<T>(args));
}
}
_
@ 1)「同期」計算を開始してバックグラウンドで実行する方法
@ 2)「非同期」および「待機」の開始方法:ここでは、メソッドが戻る前に計算が実行され完了しますが、async
/await
のためにUIはブロックされません(ところで:このようなイベントハンドラーは、イベントハンドラーがvoid
を返す必要があるため、_async void
_の唯一の有効な使用方法です-その他の場合はすべて_async Task
_を使用します))
@ 3)新しいThread
の代わりに、Task
を使用します。後で(成功)完了を確認できるように、グローバルcalcTask
メンバーに保存します。バックグラウンドでは、これにより新しいスレッドが開始され、そこでアクションが実行されますが、処理がはるかに簡単になり、その他の利点もあります。
@ 4)ここでもアクションを開始しますが、今回はタスクを返すため、「非同期イベントハンドラ」は「待機」できます。 async Task CalcAsync()
を作成してからawait Task.Run(() => calc.TestMethod(input)).ConfigureAwait(false);
を作成することもできます(FYI:ConfigureAwait(false)
はデッドロックを回避するためです。async
を使用する場合は、/await
(ここで説明するのは多すぎるでしょう)同じワークフローになりますが、_Task.Run
_が唯一の「待機可能な操作」であり、タスクを返すことができる最後の操作ですコンテキストスイッチを1つ保存すると、実行時間が節約されます。
@ 5)ここで、「強く型付けされた汎用イベント」を使用して、「ステータスオブジェクト」を簡単に受け渡しできるようにします。
@ 6)ここでは、以下で定義されている拡張機能を使用します。これにより、(使いやすさは別として)古い例で起こりうる競合状態が解決されます。そこで、イベントがnull
- checkの後、呼び出しの前にif
になった可能性がありますが、その瞬間に別のスレッドでイベントハンドラーが削除された場合です。これは、拡張機能がイベントデリゲートの「コピー」を取得し、同じ状況でハンドラがRaise
メソッド内にまだ登録されているため、ここでは発生しません。
ここでカーブボールを投げます。一度言ったら、100回言った。 Invoke
やBeginInvoke
などのマーシャリング操作は、ワーカースレッドの進行状況でUIを更新するための最良の方法とは限りません。
この場合、通常、ワーカースレッドが進捗情報を共有データ構造に公開し、UIスレッドが定期的にポーリングする方が適切です。これにはいくつかの利点があります。
Invoke
が課すUIとワーカースレッド間の密結合を解除します。BeginInvoke
がワーカースレッドから使用された場合のように、UIメッセージキューをオーバーランするリスクはありません。Invoke
の場合のように、ワーカースレッドはUIスレッドからの応答を待つ必要はありません。Invoke
とBeginInvoke
は高価な操作です。そのため、calcClass
に進捗情報を保持するデータ構造を作成します。
public class calcClass
{
private double percentComplete = 0;
public double PercentComplete
{
get
{
// Do a thread-safe read here.
return Interlocked.CompareExchange(ref percentComplete, 0, 0);
}
}
public testMethod(object input)
{
int count = 1000;
for (int i = 0; i < count; i++)
{
Thread.Sleep(10);
double newvalue = ((double)i + 1) / (double)count;
Interlocked.Exchange(ref percentComplete, newvalue);
}
}
}
次に、MainWindow
クラスでDispatcherTimer
を使用して、進捗情報を定期的にポーリングします。 DispatcherTimer
を設定して、状況に最も適した間隔でTick
イベントを発生させます。
public partial class MainWindow : Window
{
public void YourDispatcherTimer_Tick(object sender, EventArgs args)
{
YourProgressBar.Value = calculation.PercentComplete;
}
}
Dispatcher
を使用してUIスレッドのコントロールを更新するのは正しいことです。また、実行時間の長いプロセスをUIスレッドで実行しないでください。実行時間の長いプロセスをUIスレッドで非同期に実行しても、パフォーマンスの問題が発生する可能性があります。
Dispatcher.CurrentDispatcher
は、必ずしもUIスレッドではなく、現在のスレッドのディスパッチャーを返すことに注意してください。 Application.Current.Dispatcher
を使用してUIスレッドのディスパッチャへの参照を取得できる場合がありますが、利用できない場合は、UIディスパッチャをバックグラウンドスレッドに渡す必要があります。
通常、スレッド操作にはBackgroundWorker
の代わりに Task Parallel Library を使用します。使いやすいと思います。
例えば、
Task.Factory.StartNew(() =>
SomeObject.RunLongProcess(someDataObject));
どこ
void RunLongProcess(SomeViewModel someDataObject)
{
for (int i = 0; i <= 1000; i++)
{
Thread.Sleep(10);
// Update every 10 executions
if (i % 10 == 0)
{
// Send message to UI thread
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
(Action)(() => someDataObject.ProgressValue = (i / 1000)));
}
}
}
UIとやり取りするものはすべて、UIスレッドで呼び出す必要があります(凍結オブジェクトでない場合)。そのためには、ディスパッチャーを使用できます。
var disp = /* Get the UI dispatcher, each WPF object has a dispatcher which you can query*/
disp.BeginInvoke(DispatcherPriority.Normal,
(Action)(() => /*Do your UI Stuff here*/));
ここではBeginInvokeを使用します。通常、バックグラウンドワーカーはUIが更新されるのを待つ必要はありません。待ちたい場合は、Invoke
を使用できます。ただし、BeginInvokeを頻繁に高速に呼び出さないように注意する必要があります。これは非常に厄介です。
ところで、BackgroundWorkerクラスはこの種のタスクを支援します。パーセンテージなどのレポートの変更を許可し、これをバックグラウンドスレッドからUIスレッドに自動的にディスパッチします。ほとんどのスレッド<>更新UIタスクの場合、BackgroundWorkerは素晴らしいツールです。
メインスレッドに戻る必要があります(別名UI thread
)UIをupdate
するため。 UIを更新しようとする他のスレッドは、exceptions
をあらゆる場所にスローするだけです。
したがって、WPFを使用しているため、 Dispatcher
を使用できます。具体的には、このbeginInvoke
でdispatcher
を使用できます。これにより、UIスレッドで必要な処理(通常はUIの更新)を実行できます。
UI
を使用できるように、コントロール/フォームへの参照を維持することで、business
にdispatcher
を「登録」することもできます。
これが長い計算であれば、バックグラウンドワーカーになります。進行状況をサポートしています。また、キャンセルもサポートしています。
http://msdn.Microsoft.com/en-us/library/cc221403(v=VS.95).aspx
ここには、コンテンツにバインドされたTextBoxがあります。
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Debug.Write("backgroundWorker_RunWorkerCompleted");
if (e.Cancelled)
{
contents = "Cancelled get contents.";
NotifyPropertyChanged("Contents");
}
else if (e.Error != null)
{
contents = "An Error Occured in get contents";
NotifyPropertyChanged("Contents");
}
else
{
contents = (string)e.Result;
if (contentTabSelectd) NotifyPropertyChanged("Contents");
}
}
BackgroundWorker
以外は何も役に立たないように思えたので、このより良い答えを追加する必要性を感じました。これは、次のようなImageタグを持つMainWindow
というXAMLページを更新する方法です。
<Image Name="imgNtwkInd" Source="Images/network_on.jpg" Width="50" />
BackgroundWorker
プロセスを使用して、ネットワークに接続しているかどうかを表示します。
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
public partial class MainWindow : Window
{
private BackgroundWorker bw = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
// Set up background worker to allow progress reporting and cancellation
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
// This is your main work process that records progress
bw.DoWork += new DoWorkEventHandler(SomeClass.DoWork);
// This will update your page based on that progress
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
// This starts your background worker and "DoWork()"
bw.RunWorkerAsync();
// When this page closes, this will run and cancel your background worker
this.Closing += new CancelEventHandler(Page_Unload);
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
BitmapImage bImg = new BitmapImage();
bool connected = false;
string response = e.ProgressPercentage.ToString(); // will either be 1 or 0 for true/false -- this is the result recorded in DoWork()
if (response == "1")
connected = true;
// Do something with the result we got
if (!connected)
{
bImg.BeginInit();
bImg.UriSource = new Uri("Images/network_off.jpg", UriKind.Relative);
bImg.EndInit();
imgNtwkInd.Source = bImg;
}
else
{
bImg.BeginInit();
bImg.UriSource = new Uri("Images/network_on.jpg", UriKind.Relative);
bImg.EndInit();
imgNtwkInd.Source = bImg;
}
}
private void Page_Unload(object sender, CancelEventArgs e)
{
bw.CancelAsync(); // stops the background worker when unloading the page
}
}
public class SomeClass
{
public static bool connected = false;
public void DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
int i = 0;
do
{
connected = CheckConn(); // do some task and get the result
if (bw.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
Thread.Sleep(1000);
// Record your result here
if (connected)
bw.ReportProgress(1);
else
bw.ReportProgress(0);
}
}
while (i == 0);
}
private static bool CheckConn()
{
bool conn = false;
Ping png = new Ping();
string Host = "SomeComputerNameHere";
try
{
PingReply pngReply = png.Send(Host);
if (pngReply.Status == IPStatus.Success)
conn = true;
}
catch (PingException ex)
{
// write exception to log
}
return conn;
}
}
詳細: https://msdn.Microsoft.com/en-us/library/cc221403(v = VS.95).aspx
神に感謝、マイクロソフトはWPFで理解されたthatを得た:)
プログレスバー、ボタン、フォームなどのすべてのControl
には、Dispatcher
があります。実行する必要があるDispatcher
にAction
を与えると、正しいスレッドで自動的に呼び出します(Action
は関数デリゲートのようなものです)。
例があります here 。
もちろん、他のクラスからコントロールにアクセスできるようにする必要があります。 public
にしてWindow
への参照を他のクラスに渡すか、参照をプログレスバーにのみ渡すことによって。