web-dev-qa-db-ja.com

BackgroundWorkerを正しく停止する方法


2つのコンボボックスを含むフォームがあります。そして、combobox2.DataSourcecombobox1.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がビジーであり、同時に複数のアクションを実行できないことについて。
この例外を取り除く方法は?
前もって感謝します!

57
StuffHappens

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

88
HackedByChinese

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を更新しません)

28
jenovachild

メインスレッドとBackgroundWorkerの間で共有されるBackgroundWorker.CancellationPendingなどのフラグを使用する必要があります。 BackgroundWorkerを終了するには、BackgroundWorker.CancelAsync()を使用してフラグを設定するだけです。

MSDNにはサンプルがあります: http://msdn.Microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancellationpending.aspx

6
Daniel Gehriger

私の例。 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; 
        }
    }
}
3
kawa

私の場合、入金確認のためにデータベースをプールし、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();
    }
1

これらの方法を試しましたが、うまくいきませんでしたので、私の答えは少し異なります。私のコードは、データベース値が読み取られるとき、またはオブジェクトが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ブロックを使用することを忘れないでください。これが役立つことを願っています!あなたがそれが役立つと思うならば、私をマークアップしてください。

1
Stanley

この問題は、cmbDataSourceExtractor.CancelAsync()が非同期メソッドであり、cmdDataSourceExtractor.RunWorkerAsync(...) exitst時にCancel操作がまだ完了していないという事実が原因です。 cmdDataSourceExtractorが完了するのを待ってから、RunWorkerAsyncを再度呼び出してください。これを行う方法について説明します このSO質問

1
Bas Bossink

私はみんなに同意します。ただし、場合によってはさらに追加する必要があります。

IE

1)これを追加worker.WorkerSupportsCancellation = true;

2)次のことを行うメソッドをクラスに追加します

public void KillMe()
{
   worker.CancelAsync();
   worker.Dispose();
   worker = null;
   GC.Collect();
}

したがって、アプリケーションを閉じる前に、このメソッドを呼び出す必要があります。

3)おそらくDispose, nullBackgroundWorker内にあるすべての変数とタイマーを使用できます。

0
Developer