Laden...

Liste im Hintergrund (Thread?) abarbeiten

Erstellt von JMano vor 9 Jahren Letzter Beitrag vor 9 Jahren 2.735 Views
J
JMano Themenstarter:in
38 Beiträge seit 2013
vor 9 Jahren
Liste im Hintergrund (Thread?) abarbeiten

Hallo,

Ich habe eine ziemlich einfach WPF GUI.
Dabei ist eine ListView die auf eine ObservableCollection<ProcObjects> binded.
Jedes von diesen ProcObjects muss abgearbeitet werden (Objekte beinhalten nur URIs (File/http)), was einiges an Zeit brauchen kann – daher natürlich nicht im GUI Thread.

Jetzt habe ich dazu ein paar Fragen wie ich das am besten im Hintergrund machen kann.
Nach dem bearbeiten des Objektes muss das natürlich auch wissen dass es Fertig ist (+ Erfolg /Misserfolg) um das in der Liste dazustellen

  1. Wie kann ich es am saubersten anstellen, dass die Objekte dass dann mitbekommen (also Thread Rückgabe wert)
  2. Wo/Wie handle ich am besten die Threads?
    Ich dachte an zwei verschiedene Optionen
    -- a. In den Objekten selbst eine Methode die das macht. Beim Interieren rufe ich die Methode einfach auf alle auf.
    -- b. Ich gehe in einer Schleife über die liste und rufe eine Methode auf die die Objekte verarbeitet ( also an die ich es übergebe)

Bei beiden natürlich klar, dass nicht auf das Ergebnis gewartet wird, das soll denn auch im Background „zurückkommen“ und das „bearbeitet-Flag“ im zugehörigen Objekt updaten.

H
523 Beiträge seit 2008
vor 9 Jahren

Schau Dir mal den BackgroundWorker an. Der macht genau das was Du brauchst.

Du kannst die Liste mit Objekten als Argument an die RunWorkerAsync-Methode des BackgroundWorkers übergeben.
Dabei musst Du beachten, dass Du in WPF einen Dispatcher brauchst, um Elemente die an die GUI gebunden sind in einem anderen als dem GUI-Thread zu verändern.

D
500 Beiträge seit 2007
vor 9 Jahren

Hallo zusammen,

ja, man kann es ueber BackgroundWorker implementieren, wuerde ich aber nicht mehr verwenden. Es ist empfehlenswert die TPL bzw. die Task Klasse dafuer zu benutzen. Dazu nimmst Du noch die Progress<T> Klasse, die den Kontext Switch fuer Dich uebernimmt und einen Zustandsfortschritt reportiert.

@Hypersurf: Such einfach einfach mal nach TPL, Task<T>, Parallel.Foreach und Listen. Da wirst Du sicherlich etwas finden und daraus, dann ableiten koennen, wie du asynchrony und dann noch parallel Deine Liste verarbeitest
z.B. Parallel.Foreach

@hypersurf: Wenn man die UI-Elemente direkt aus dem BackgroundWorker veraendert, dann hast Du Recht. Man muss sich um den Kontext Switch kuemmern. Wenn man allerdings "nur" die Events aus dem BackgroundWorker verwendet, dann uebernimmt der BG den Kontext Switch fuer Dich.

Gruesse,
Moe

6.911 Beiträge seit 2009
vor 9 Jahren

Hallo JMano,

schau dir [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke) an und beachte bitte vor weiteren Nachfragen [Hinweis] Wie poste ich richtig? Punkt 1.1, 1.1.1.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

H
523 Beiträge seit 2008
vor 9 Jahren

@Hypersurf: Such einfach einfach mal nach TPL, Task<T>, Parallel.Foreach und Listen. Da wirst Du sicherlich etwas finden und daraus, dann ableiten koennen, wie du asynchrony und dann noch parallel Deine Liste verarbeitest
z.B.
>

Hier war vermutlich nicht ich gemeint. Mir sind die Task-Klassen bekannt 😉

Wenn man allerdings "nur" die Events aus dem BackgroundWorker verwendet, dann uebernimmt der BG den Kontext Switch fuer Dich.

Was meinst Du damit? Wenn ich Elemente im DoWork verändere, dann übernimmt der BG keinen Kontext Switch für mich.

W
955 Beiträge seit 2010
vor 9 Jahren

Hallo,

Hier war vermutlich nicht ich gemeint. Mir sind die Task-Klassen bekannt 😉

Dann schlage sie ihm doch vor. Der BGW ist im Ruhestand.

Was meinst Du damit? Wenn ich Elemente im DoWork verändere, dann übernimmt der BG keinen Kontext Switch für mich. Aber ProgressChanged und RunWorkerCompleted arbeiten dann wieder im Context der den BGW erstellt hat.

D
500 Beiträge seit 2007
vor 9 Jahren

Hi,

richtig, Du warst nicht gmeint hypersurf, sondern JMano.

Was meinst Du damit? Wenn ich Elemente im DoWork verändere, dann übernimmt der BG keinen Kontext Switch für mich.

Du hast natuerlich Recht, ich war zu ungenau in meiner Aussage. Ich meinte lediglich RunWorkerCompleted und ProgressChanged. Ich hatte vergessen, dass DoWork natuerlich auch ein Event ist und da muss man sich um den Kontextwechsel selber kuemmern, wenn man direkt auf UI Elemente zugreift.
Ich habe es immer so gehalten, dass ich aus dem DoWork selber keine UI-Manipulationen vorgenommen habe. In dem asynchron verarbeitenden BG-Worker habe ich lediglich die reine UI-unabhaengige Algorithmen ausgefuehrt. Ueber die ReportProgress-Methode dann Informationen weitergegeben. Ein iteressierter Abonnent hat sich auf dem ProgressChanged registriert, und dann entsprechend z.B. die UI aktualisiert.
So habe ich die Algorithmik frei von UI-Aktualisierung und vor allem dem Kontextwechsel. Das uebernimmt dann der BG fuer mich, wenn ich mit ProgressChanged-Event arbeite.
Der Abonnent, hier die UI, weiss am besten, was zu aktualisieren ist. So hat man beides schoen getrennt.
Natuerlich muss man sich noch etwas ueberlegen, wenn man mit ReportProgress arbeitet und dir der reine Zustand in Prozent nicht genuegt. Dafuer gibt es eine Ueberladung von ReportProgress, die noch einen UserState weitergibt. Dafuer verwendet man dann einen Progress Datencontainer (ist frei nach eigenen Anforderungen zu waehlen), der irgendwelche Nachrichten oder was auch immer enthaelt. Die UI-Seite entnimmt dann wieder diese Informationen und setzt den Progress, schreibt einen Zustand, etc.

Gruss,
DaMoe

J
JMano Themenstarter:in
38 Beiträge seit 2013
vor 9 Jahren

Nachdem ich den BGWorker schon einmal genutzt habe, hab ich mich jetzt auch dazu entschieden.
Habe die Methoden dazu im ListenObjekt verpackt.

Habe aber mit dem GUI update scheinbar noch immer ein Problem.
Aber vl zuerst mal bischen genauere Beschreibung:

Meine GUI ist einfach mit paar Buttons und einer Listbox

aus der .xaml


  <ListBox DockPanel.Dock="Top" Margin="0,0,0,5" Name="LbUris" ItemsSource="{Binding}" />

aus der xaml.cs


  UriList = new ObservableCollection<UriObject>();
  LbUris.ItemsSource = UriList;

zur liste kann ich per zB.: drag&drop Daten hinzufügen was schlussendlich über ... passiert


        public void AddToList(string uri)
        {
            UriObject uriObj = new UriObject(url);

            uriObj.AddId = counter;
            UriList.Add(uriObj);
            counter++;

            if (ProcessStartAutomatically)
            {
                 uriObj.DoWorkOnObject();
            }
        }

Und hier die Object classe


  internal class uriObj : INotifyPropertyChanged
  {
      public event PropertyChangedEventHandler PropertyChanged;

      private bool IsProcessed;

      public void DoWorkOnObject()
      {
          BackgroundWorker bgWorker;
          bgWorker = new BackgroundWorker();
          bgWorker.DoWork += new DoWorkEventHandler(BW_DoWork);
          bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BW_Complete);

          var myArgs = new MyArgs();
          myArgs.IsDone = false;
          myArgs.Uri = this.uri;
          myArgs.Title = this.title;

          bgWorker.RunWorkerAsync(myArgs);
      }

      public override string ToString()
      {
          string state = "   New   ";

          if (IsProcessed)
          {
              state = "Done";
          }

          return string.Format("[{0}]\t{1}", state, uri);
      }

      private void BW_DoWork(object sender, DoWorkEventArgs e)
      {
          MyArgs myArgs = (MyArgs)e.Argument;
          // do the Work .... with myArgs.Uri
          myArgs.IsDone = true;
          e.Result = myArgs;
      }

      private void BW_Complete(object sender, RunWorkerCompletedEventArgs e)
      {
          if (e.Error == null)
          {
              MyArgs myArgs = (MyArgs)e.Result;
              this.IsProcessed = myArgs.IsDone;
          }

          this.NotifyPropertyChanged("IsProcessed");
      }

      private void NotifyPropertyChanged(string propertyName = "")
      {
          if (PropertyChanged != null)
          {
              PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
          }
      }
  }


Dabei soll dann in der Listbox auf der GUI einfach für jedes Object eine Line sein, die das Object repräsentiert.
Durch die Override von ToString() soll das auch angepasst an den State des Objekts passieren.
Nur wenn die Bearbeitung abgeschlossen ist, passiert nichts.
Hab gelesen, dass ich eben das INotifyPropertyChanged implementieren muss weil die ObservableCollection nur bei Add/remove "reagiert".

Nur passiert eben so auch nicht ... weil das PropertyChanged Event keinen Handler (PropertyChanged) hat.
Muss ich den tatsächlich selbst implementieren das ich ein 'LbUris.Items.Refresh();' ausführen kann, oder sollte da irgendwo etwas automatisch passieren, was ich nicht sehe?

771 Beiträge seit 2009
vor 9 Jahren

Wenn du willst, dass sich die ItemSource aktualisiert, dann musst du diese auch angeben:


this.NotifyPropertyChanged("ItemsSource");

Edit: Du musst also selber ein Property in deiner Klasse UriList erstellen (welche die ObservableCollection hält) und daran binden - und den Propertynamen dann statt "ItemsSource" einsetzen! Ein Beispiel gibt es bei INotifyPropertyChanged and ObservableCollection WPF (das Property in der Antwort von m-y heißt dort "Campers").

Oder reagierst du irgendwo noch auf "IsProcessed" (dann muss es aber sowieso ein Property sein!)?

Designtechnisch ist das aber sicherlich nicht der beste Weg, da deine Klasse UriObject sicherlich nicht direkt wissen sollte, dass sie an "ItemsSource" gebunden ist. Da gibt es ein paar andere Ansätze dafür (z.B. eine eigene Klasse von ObservableCollection ableiten oder aber Event Delegation z.B. per CollectionView), aber erst mal muss es überhaupt funktionieren.

Und auch die Verwendung vom BG bei WPF ist auch eher bei Übernahme von Altprojekten (aus WinForms) zu sehen; ich würde auch eher (wie DaMoe80) die TPL nutzen.