webentwicklung-frage-antwort-db.com.de

Wie man BackgroundWorker richtig anhält


Ich habe ein Formular mit 2 Comboboxen. Und ich möchte combobox2.DataSource basierend auf combobox1.Text und combobox2.Text füllen (ich gehe davon aus, dass der Benutzer die Eingabe in combobox1 abgeschlossen hat und sich gerade in combobox2 eingibt). Ich habe also einen Event-Handler für combobox2 wie folgt:

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 );
}

Da das Erstellen von DataSource ein zeitaufwändiger Prozess ist (es erstellt eine Anforderung an die Datenbank und führt sie aus), entschied ich, dass es besser ist, sie in einem anderen Prozess mit BackgroundWorker auszuführen. Es gibt also ein Szenario, in dem cmbDataSourceExtractor seine Arbeit nicht abgeschlossen hat und der Benutzer ein weiteres Symbol eingibt. In diesem Fall bekomme ich eine Ausnahme in dieser Zeile
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues ); über diesen BackgroundWorker ist beschäftigt und kann nicht mehrere Aktionen gleichzeitig ausführen.
Wie diese Ausnahme loswerden?
Danke im Voraus!

55
StuffHappens

CancelAsync bricht Ihren Thread oder ähnliches nicht ab. Es sendet eine Nachricht an den Arbeitsthread, dass die Arbeit über BackgroundWorker.CancellationPending abgebrochen werden soll. Ihr DoWork-Delegat, der im Hintergrund ausgeführt wird, muss diese Eigenschaft regelmäßig überprüfen und die Stornierung selbst ausführen.

Der schwierige Teil ist, dass Ihr DoWork-Delegierter wahrscheinlich blockiert, was bedeutet, dass die Arbeit, die Sie an Ihrer DataSource ausführen, abgeschlossen sein muss, bevor Sie irgendetwas anderes tun können (z. B. Check für CancellationPending). Möglicherweise müssen Sie Ihre eigentliche Arbeit an einen anderen asynchronen Delegierten verschieben (oder vielleicht noch besser, die Arbeit an die ThreadPool übergeben) und Ihren Haupt-Worker-Thread abfragen, bis dieser innere Worker-Thread einen Wartestatus auslöst, OR erkennt CancellationPending.

http://msdn.Microsoft.com/de-de/library/system.componentmodel.backgroundworker.cancelasync.aspx

http://www.codeproject.com/KB/cpp/BackgroundWorker_Threads.aspx

87
HackedByChinese

Wenn Sie eine Schleife zwischen CancelAsync () und RunWorkerAsync () hinzufügen, wird Ihr Problem dadurch gelöst

 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 );
  }

Die while-Schleife mit dem Aufruf von Application.DoEvents () hault die Ausführung Ihres neuen Arbeitsthreads, bis der aktuelle ordnungsgemäß abgebrochen wurde. Denken Sie daran, dass Sie immer noch den Abbruch Ihres Arbeitsthreads erledigen müssen. Mit so etwas wie:

 private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e)
 {
      if (this.cmbDataSourceExtractor.CancellationPending)
      {
          e.Cancel = true;
          return;
      }
      // do stuff...
 }

Das Application.DoEvents () im ersten Code-Snippet verarbeitet die GUI-Threads-Nachrichtenwarteschlange weiter, sodass auch die cmbDataSourceExtractor.IsBusy-Eigenschaft abgebrochen und aktualisiert werden kann (wenn Sie statt "Application.DoEvents ()" einfach einen "Continue" hinzufügen Die Schleife würde den GUI-Thread in einem ausgelasteten Zustand sperren und das Ereignis zum Aktualisieren von cmbDataSourceExtractor.IsBusy nicht verarbeiten.

26
jenovachild

Sie müssen ein Flag verwenden, das vom Hauptthread und dem BackgroundWorker gemeinsam genutzt wird, z. B. BackgroundWorker.CancellationPending. Wenn der BackgroundWorker beendet werden soll, setzen Sie das Flag einfach mit BackgroundWorker.CancelAsync ().

MSDN verfügt über ein Beispiel: http://msdn.Microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancellationpending.aspx

6
Daniel Gehriger

Mein Beispiel DoWork ist unten:

    DoLengthyWork();

    //this is never executed
    if(bgWorker.CancellationPending)
    {
        MessageBox.Show("Up to here? ...");
        e.Cancel = true;
    }

in DoLenghtyWork:

public void DoLenghtyWork()
{
    OtherStuff();
    for(int i=0 ; i<10000000; i++) 
    {  int j = i/3; }
}

in OtherStuff ():

public void OtherStuff()
{
    for(int i=0 ; i<10000000; i++) 
    {  int j = i/3; }
}

Was Sie tun möchten, ist, DoLenghtyWork und OtherStuff () so zu ändern, dass sie zu:

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

Meine Antwort ist etwas anders, weil ich diese Methoden ausprobiert habe, aber sie funktionierten nicht. Mein Code verwendet eine zusätzliche Klasse, die auf ein Boolean-Flag in einer öffentlichen statischen Klasse prüft, wenn die Datenbankwerte gelesen werden oder wo ich es vorziehen möchte, bevor ein Objekt einem List-Objekt hinzugefügt wird. Siehe die Änderung im Code unten. Ich habe die Eigenschaft ThreadWatcher.StopThread hinzugefügt. Für diese Erklärung werde ich den aktuellen Thread nicht wiederherstellen, da es nicht Ihr Problem ist, aber das ist so einfach wie das Festlegen der Eigenschaft auf false, bevor auf den nächsten Thread zugegriffen wird ... 

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 );
  }

alles gut

private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e)
 {
      if (this.cmbDataSourceExtractor.CancellationPending)
      {
          e.Cancel = true;
          return;
      }
      // do stuff...
 }

Fügen Sie nun die folgende Klasse hinzu

public static class ThreadWatcher
{
    public static bool StopThread { get; set; }
}

und in Ihrer Klasse, in der Sie die Datenbank lesen

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);
...

vergessen Sie nicht, einen finally-Block zu verwenden, um Ihre Datenbankverbindung ordnungsgemäß zu schließen. Hoffen Sie, dass dies hilft! Bitte kennzeichnen Sie mich, wenn Sie es hilfreich finden.

1
Stanley

Das Problem wird durch die Tatsache verursacht, dass cmbDataSourceExtractor.CancelAsync() eine asynchrone Methode ist. Die Cancel-Operation ist noch nicht abgeschlossen, wenn cmdDataSourceExtractor.RunWorkerAsync(...) exitst. Sie sollten warten, bis cmdDataSourceExtractor abgeschlossen ist, bevor Sie RunWorkerAsync erneut aufrufen. Wie das geht, wird erklärt in dieser SO Frage .

1
Bas Bossink

In meinem Fall musste ich die Datenbank zusammenfassen, um die Zahlungsbestätigung zu erhalten, und dann WPF UI aktualisieren.

Mechanismus, der alle Prozesse auflädt:

public void Execute(object parameter)
        {
            try
            {
                var amount = ViewModel.Amount;
                var transactionId = ViewModel.TransactionMain.TransactionId.ToString();
                var productCode = ViewModel.TransactionMain.TransactionDetailList.First().Product.ProductCode;
                var transactionReference = GetToken(amount, transactionId, productCode);
                var url = string.Format("{0}New?transactionReference={1}", Settings.Default.PaymentUrlWebsite, transactionReference);
                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");
            }
        }

Mechanismus, der die Fertigstellung überprüft:

 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();
    }

Mechanismus, der abbricht, wenn Fenster geschlossen wird:

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();
    }
0

Ich stimme mit den Jungs überein. Aber manchmal muss man noch mehr hinzufügen.

IE

1) Diesen worker.WorkerSupportsCancellation = true; hinzufügen

2) Fügen Sie Ihrer Klasse eine Methode hinzu, um die folgenden Dinge zu tun

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

Bevor Sie also Ihre Anwendung schließen, müssen Sie diese Methode aufrufen.

3) Wahrscheinlich können Sie Dispose, null alle Variablen und Timer innerhalb von BackgroundWorker verwenden.