2つのコンボボックスを含むフォームがあります。そして、combobox2.DataSource
とcombobox1.Text
に基づいてcombobox2.Text
を埋めたいと思います(ユーザーがcombobox1
への入力を完了し、combobox2
への入力の途中にいると仮定します)。したがって、次のようなcombobox2
のイベントハンドラがあります。
private void combobox2_TextChanged(object sender, EventArgs e)
{
if (cmbDataSourceExtractor.IsBusy)
cmbDataSourceExtractor.CancelAsync();
var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
V2 = combobox2.Text};
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}
DataSourceの構築が時間のかかるプロセスである限り(データベースへの要求を作成して実行する)、BackgroundWorkerを使用して別のプロセスで実行する方が良いと判断しました。そのため、cmbDataSourceExtractorが作業を完了せず、ユーザーがもう1つの記号を入力するシナリオがあります。この場合、この行で例外が発生しますcmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
BackgroundWorkerがビジーであり、同時に複数のアクションを実行できないことについて。
この例外を取り除く方法は?
前もって感謝します!
CancelAsync
は実際にはスレッドなどを中止しません。 BackgroundWorker.CancellationPending
を介して作業をキャンセルする必要があるというメッセージをワーカースレッドに送信します。バックグラウンドで実行されているDoWorkデリゲートは、定期的にこのプロパティをチェックし、キャンセル自体を処理する必要があります。
トリッキーな部分は、DoWorkデリゲートがおそらくブロックしていることです。つまり、DataSourceで行う作業は、CancellationPendingの確認など、他の操作を行う前に完了する必要があります。実際の作業をさらに別の非同期デリゲートに移動する必要があるかもしれません(または、もっと良いのは、ThreadPool
に作業を送信する)、この内部ワーカースレッドが待機状態をトリガーするまで、メインワーカースレッドにポーリングさせるOR CancellationPendingを検出します。
http://msdn.Microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancelasync.aspx
http://www.codeproject.com/KB/cpp/BackgroundWorker_Threads.aspx
CancelAsync()とRunWorkerAsync()の間にループを追加すると、問題が解決します
private void combobox2_TextChanged(object sender, EventArgs e)
{
if (cmbDataSourceExtractor.IsBusy)
cmbDataSourceExtractor.CancelAsync();
while(cmbDataSourceExtractor.IsBusy)
Application.DoEvents();
var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
V2 = combobox2.Text};
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}
Application.DoEvents()の呼び出しを伴うwhileループは、現在のワーカースレッドが適切にキャンセルされるまで、新しいワーカースレッドの実行を中断します。ワーカースレッドのキャンセルを処理する必要があることに留意してください。次のようなもので:
private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e)
{
if (this.cmbDataSourceExtractor.CancellationPending)
{
e.Cancel = true;
return;
}
// do stuff...
}
最初のコードスニペットのApplication.DoEvents()は引き続きGUIスレッドメッセージキューを処理するため、cmbDataSourceExtractor.IsBusyプロパティをキャンセルして更新するイベントも処理されます(Application.DoEvents()の代わりにcontinueを追加した場合)ループはGUIスレッドをビジー状態にロックし、イベントを処理してcmbDataSourceExtractor.IsBusyを更新しません)
メインスレッドとBackgroundWorkerの間で共有されるBackgroundWorker.CancellationPending
などのフラグを使用する必要があります。 BackgroundWorkerを終了するには、BackgroundWorker.CancelAsync()を使用してフラグを設定するだけです。
MSDNにはサンプルがあります: http://msdn.Microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancellationpending.aspx
私の例。 DoWorkは以下のとおりです。
DoLengthyWork();
//this is never executed
if(bgWorker.CancellationPending)
{
MessageBox.Show("Up to here? ...");
e.Cancel = true;
}
doLenghtyWork内:
public void DoLenghtyWork()
{
OtherStuff();
for(int i=0 ; i<10000000; i++)
{ int j = i/3; }
}
otherStuff()内:
public void OtherStuff()
{
for(int i=0 ; i<10000000; i++)
{ int j = i/3; }
}
DoLenghtyWorkとOtherStuff()の両方を変更して、次のようにします。
public void DoLenghtyWork()
{
if(!bgWorker.CancellationPending)
{
OtherStuff();
for(int i=0 ; i<10000000; i++)
{
int j = i/3;
}
}
}
public void OtherStuff()
{
if(!bgWorker.CancellationPending)
{
for(int i=0 ; i<10000000; i++)
{
int j = i/3;
}
}
}
私の場合、入金確認のためにデータベースをプールし、WPF
UIを更新する必要がありました。
すべてのプロセスを起動するメカニズム:
public void Execute(object parameter)
{
try
{
var url = string.Format("{0}New?transactionReference={1}", Settings.Default.PaymentUrlWebsite, "transactionRef");
Process.Start(new ProcessStartInfo(url));
ViewModel.UpdateUiWhenDoneWithPayment = new BackgroundWorker {WorkerSupportsCancellation = true};
ViewModel.UpdateUiWhenDoneWithPayment.DoWork += ViewModel.updateUiWhenDoneWithPayment_DoWork;
ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerCompleted += ViewModel.updateUiWhenDoneWithPayment_RunWorkerCompleted;
ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerAsync();
}
catch (Exception e)
{
ViewModel.Log.Error("Failed to navigate to payments", e);
MessageBox.Show("Failed to navigate to payments");
}
}
完了の確認を行うメカニズム:
private void updateUiWhenDoneWithPayment_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(30000);
while (string.IsNullOrEmpty(GetAuthToken()) && !((BackgroundWorker)sender).CancellationPending)
{
Thread.Sleep(5000);
}
//Plug in pooling mechanism
this.AuthCode = GetAuthToken();
}
ウィンドウが閉じた場合にキャンセルするメカニズム:
private void PaymentView_OnUnloaded(object sender, RoutedEventArgs e)
{
var context = DataContext as PaymentViewModel;
if (context.UpdateUiWhenDoneWithPayment != null && context.UpdateUiWhenDoneWithPayment.WorkerSupportsCancellation && context.UpdateUiWhenDoneWithPayment.IsBusy)
context.UpdateUiWhenDoneWithPayment.CancelAsync();
}
これらの方法を試しましたが、うまくいきませんでしたので、私の答えは少し異なります。私のコードは、データベース値が読み取られるとき、またはオブジェクトがListオブジェクトなどに追加される直前に、パブリック静的クラスのブールフラグをチェックする追加のクラスを使用します。以下のコードの変更を参照してください。 ThreadWatcher.StopThreadプロパティを追加しました。この説明では、あなたの問題ではないが、次のスレッドにアクセスする前にプロパティをfalseに設定するのと同じくらい簡単なので、現在のスレッドを元に戻すつもりです...
private void combobox2_TextChanged(object sender, EventArgs e)
{
//Stop the thread here with this
ThreadWatcher.StopThread = true;//the rest of this thread will run normally after the database function has stopped.
if (cmbDataSourceExtractor.IsBusy)
cmbDataSourceExtractor.CancelAsync();
while(cmbDataSourceExtractor.IsBusy)
Application.DoEvents();
var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
V2 = combobox2.Text};
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}
大丈夫だ
private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e)
{
if (this.cmbDataSourceExtractor.CancellationPending)
{
e.Cancel = true;
return;
}
// do stuff...
}
次のクラスを追加します
public static class ThreadWatcher
{
public static bool StopThread { get; set; }
}
そして、データベースを読むクラスで
List<SomeObject>list = new List<SomeObject>();
...
if (!reader.IsDbNull(0))
something = reader.getString(0);
someobject = new someobject(something);
if (ThreadWatcher.StopThread == true)
break;
list.Add(something);
...
データベース接続などを適切に閉じるために、finallyブロックを使用することを忘れないでください。これが役立つことを願っています!あなたがそれが役立つと思うならば、私をマークアップしてください。
この問題は、cmbDataSourceExtractor.CancelAsync()
が非同期メソッドであり、cmdDataSourceExtractor.RunWorkerAsync(...)
exitst時にCancel
操作がまだ完了していないという事実が原因です。 cmdDataSourceExtractor
が完了するのを待ってから、RunWorkerAsync
を再度呼び出してください。これを行う方法について説明します このSO質問 。
私はみんなに同意します。ただし、場合によってはさらに追加する必要があります。
IE
1)これを追加worker.WorkerSupportsCancellation = true;
2)次のことを行うメソッドをクラスに追加します
public void KillMe()
{
worker.CancelAsync();
worker.Dispose();
worker = null;
GC.Collect();
}
したがって、アプリケーションを閉じる前に、このメソッドを呼び出す必要があります。
3)おそらくDispose, null
BackgroundWorker
内にあるすべての変数とタイマーを使用できます。