AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Programmieren allgemein [Fmx] BackgroundWorker Verwendung als TListView DataProvider
Thema durchsuchen
Ansicht
Themen-Optionen

[Fmx] BackgroundWorker Verwendung als TListView DataProvider

Ein Thema von Rollo62 · begonnen am 11. Apr 2016 · letzter Beitrag vom 12. Apr 2016
Antwort Antwort
Rollo62

Registriert seit: 15. Mär 2007
4.093 Beiträge
 
Delphi 12 Athens
 
#1

[Fmx] BackgroundWorker Verwendung als TListView DataProvider

  Alt 11. Apr 2016, 00:17
Hallo zusammen,

ich habe mir mal den BackgroundWorker von Sir Rufo angesehen, und versuche den mit anonymen Prozeduren zusammenzubringen um evtl. das Handling z.B. mit TListView zu vereinfachen.

Erstmal dankesehr für diese Unit, das ist wirklich schöner Threading-Code.

Weil anscheinen LiveBinding nicht verlässlich funktioniert und ich auch die ListViews nicht komplett von Hand setzen möchte suche ich nach einfachen Wegen wie man das machen kann.
Insbesondere möchte ich auch die Bitmaps in der ListView einfach benutzen, und zwar so das es auf allen Platformen läuft.

Das Problem ist das Laden von Daten in die ListView kann die App ziemlich lange einfrieren, das möchte ich möglichst optimieren und mit BackgroundWorker-Threads verstecken was eben geht.

Mein erster Versuch sieht so aus:
Ich habe eine Klasse vom BackgroundWorker abgeleitet, die dann im Hintergrund Daten verarbeiten soll um diese der ListView dann zuzuordnen.
Dabei soll das Vorbereiten und Holen der Daten im Hintergrund ablaufen, Das Übertragen der Daten wird dann wieder im UI-Thread gemacht.

Damit alles schön übersichtlich bleibt möchte ich anonyme Prozeduren benutzen (sorry das Demo ist schon etwas unübersichtlich, aber das kommt durch die verschiedenen Versuche, wir sich hoffentlich bald lichten).

Im Aufruf benutze ich class procedures um einfach und direkt Daten einzupflegen.
Diese benutzt drei anonyme Prozeduren für
- Prepare (UI-Thread stuff)
- Fetch (Abfrage von Daten durch Thread, sollte threadsafe sein)
- Complete (UI-Thread stuff)

und übergibt weitere Parameter
- für die ListView InsertPosition
- und Von/Bis Parameter für die Fetch-Bedingung

Das sollte nur ein grundsätzliches Beispiel sein, ob es so mit BackgroundWorker funktioniert und Sinn macht,
das Fetch könnte dann auch von einem DataSet kommen, oder Images von einem Fiolder im Hintergrund lesen, etc.

Der Gedanke ist jedenfalls:
- ich rufe TS4ListView_Worker.Items_Add( auf um mein ListView zu füllen oder zu aktualisieren
- Das Items_Add kann je nach Parameter per Append oder Insert neue Daten an bestimmte Positionen BULK-laden
- ListView wird im Hintergrund bearbeitet, die Daten werden im DoAdd bereitgestellt.
- danach wird die ListView wieder zum Bearbeiten freigegeben, der TS4ListView_Worker gibt sich selbst frei
- das Ganze ListView Handling ist bestmöglich in der Klasse gekapselt, so das man nur dafür sorgen muss das
die Daten für jeden Record beschafft werden

Delphi-Quellcode:
  
//
... vereinfachter Code
//
procedure TForm1.DoAdd(iPosition, iCount : Integer);
var
  lvw : TS4ListView_Worker;

begin

lvw := TS4ListView_Worker.Items_Add(ListView1,
                              //
                              // PrepareProc: Begin process, Prepare UI
                              //
                              procedure
                              begin

                                AniIndicator1.Enabled := True;
                                AniIndicator1.Visible := True;
                                AniIndicator1.Repaint;
                                ListView1.Enabled := False;
                                ListView1.BeginUpdate;

                              end,
                              //
                              // WorkProc: Fetch the single items syncd from background
                              //
                              procedure (const ALvi : TListViewItem;
                                               AIdNew : Integer;
                                         var AColumnData : TS4ListView_Worker.TColumnData
                                        )
                              begin
                                  //
                                  //
                                  ... vereinfachter Code
                                  //
                                  //

                                  //
                                  // Setup the ColumnsData record here
                                  //
                                  AColumnData.AId := AIdNew;
                                  AColumnData.AText := 'New ' + AIdNew.ToString;
                                  AColumnData.ADetail := 'More Details on New ' + AIdNew.ToString +
                                                         ' Img. ' + iImg.ToString;
                                  AColumnData.ABmp := bmp;

                              end,
                             //
                             // CompleteProc: End the process, release UI change
                             //
                             procedure (e : TS4ListView_Worker.TCompletedEventArgs)
                             begin

                               AniIndicator1.Enabled := False;
                               AniIndicator1.Visible := False;
                               ListView1.EndUpdate;
                               ListView1.Enabled := True;
                               ListView1.Repaint;

                               Label1.Text := ListView1.Items.Count.ToString + ' items';

                               lvw.Free; // Is this really working, to avoid memory leaks ???

                             end,
                             //
                             // Pre-Settings for the Fetch process
                             //
                             iPosition, // Insert Position in Items
                             iFetchFrom, // Fetch conditions, used in WorkProc to determin from/to
                             iFetchTo
                           );
In dem ListView_Worker bearbeite ich die Prozeduren in verschiedenen Steps, und synchronisiere die ListView relevanten Zugriffe, wenn nötig.

Delphi-Quellcode:
procedure TS4ListView_Worker.NotifyDoWork(e: TDoWorkEventArgs);
var
  I: Integer;

begin

  //
  // First Prepare possible States in the UI-Thread, if needed
  //
  if Assigned(FCd_Prepare_Proc) then
  begin
    TThread.Synchronize(nil,
                  procedure
                  begin
                    FCd_Prepare_Proc;
                  end
                 );
  end;

  //
  // 1.Part: Prepare new List Items safely in the UI-Thread
  //
  // !! Firstly only the visible items, to accelerate the UX
  //
  TThread.Synchronize(nil,
                procedure
                var
                  I, iCount : Integer;
                begin

      iCount := FIdNewTo - FIdNewFrom;

      if FInsertPos < 0 then
      begin
        for I := 0 to iCount-1 do
        begin
          if I = 20 then
            Sleep(20); // TEST: Sleep a little, to allow fastest UI repaint

          FListViewItems[I] := FListView.Items.Add as TListViewItem;
        end;
      end
      else
      begin
        for I := 0 to iCount-1 do
        begin
          if I = 20 then
            Sleep(20); // TEST: Sleep a little, to allow fastest UI repaint

          FListViewItems[I] := FListView.Items.Insert(FInsertPos) as TListViewItem;
        end;
      end;

                end
               );




  //
  // Finally Start to fetch the new data WITHIN the BackgroundThread
  //
  if Assigned(FCd_Work_Proc) then
  begin

    if (FIdNewTo - FIdNewFrom) > 0 then
    begin

      for I := 0 to (FIdNewTo - FIdNewFrom)-1 do
      begin
        if Assigned(FListViewItems[I]) then
        begin
          FColumnDatas[I].AId := FIdNewFrom + I;
          FColumnDatas[I].ABmp := nil;
          FColumnDatas[I].ABmpRef := nil;

          FCd_Work_Proc(FListViewItems[I],
                        FIdNewFrom + I,
                        FColumnDatas[I]); // Fetch the data here
        end;
      end

    end;

  end;

  inherited;

end;
Als Beispiel lade ich Texte und Bilder, und zeige bei der Verarbeitung einen Animator an.
- Texte funktionieren ja ganz gut (CheckBox Bitmap abgewählt), aber ich bin nicht ganz sicher ob die
Ausführung so Speicherlecks erzeugen wird (zumindest gibt es Unterschiede unter diversen Plattformen).

- Bei Bitmaps fängt das Problem schon an, die hole ich mir der Einfachheit halber aus einer ImageList,
idealerweise sollten diese als komprimierte PNG Bilder gespeichert sein um wenig Platz und unter
allen Platformen zu Laufen.

Es können aber sporadisch Bitmaps in der Anzeige fehlen, manchmal geht es aber auch,
ich vermute mal das Bitmaps der ImageList auch im UI-Thread bearbeitet werden möchten.
Je nachdem welche Methode man benutzt funktioniert es besser oder schlechter, aner ich habe da auch das
Abfragen und Generieren der Bitmaps via ImageList im Verdacht.
Jedenfalls habe ich andere Methoden (Lesen aus Folder) noch nicht ausprobiert, es geht im Wesentlichen
erstmal um das generelle Konzept einer solchen Klasse.

Ich habe probiert:
- Speichern via Stream als PNG
- direktes Assign
- über BitmapRef nur die Referenz zu übergeben
- per z.B. TGlyph die Images über eine Komponente zu holen (was aber im Thread ein Problem ist)

Ich vermute mal: Bitmaps im Thread sind ein No-Go. Oder gibt es noch einen Trick mit Bitmaps ?

- Wenn man mal größere Mengen einträgt wird das Ganze schnell träger und kann Probleme machen, das
passiert bei mir schon ab 5000 - 20000 Einträgen.
Da würde ich eingentlich kein Speicherproblem erwarten, im TaskManager zeigt das nur 170MB belegt werden.
Aber ein Click zum Eintragen kann schon recht zäh verzögert werden, die interne Verarbeitung der ListView
scheint schon bei 20000 Textrecords ziemlich verlangsamt zu werden.
Ich sehe aber nicht das es MemoryLeaks wären, kann mich aber auch irren.

- Eine Frage wäre innerhalb der class procedure, ob sich nach CompleteProc die ganze Klasse selbst freigeben kann ?
Delphi-Quellcode:
    Result := TS4ListView_Worker.Create(AListView, // The target tLv to Add an item
                                        AProcPrepare,
                                        AProcWork, // the ColumnData Getter Proc
                                        AProcComplete,
                                        iInsertPos,
                                        iNewIdFrom,
                                        iNewIdTo
                                       );

     if Assigned(Result) then
     begin
       Result.RunWorkerAsync;
     end;
Oder ob man die Variable mitführen und von Hand freigeben muss ?
Es scheint eigentlich zu funktionieren, aber bin mir noch nicht 100% sicher ob es auch korrekt ist.

Bitte schaut euch mal das Demo und den Versuch an, es wäre schön ein paar hilfreiche Kommentare zu bekommen
ob dies ein sinnvoller Weg ist, oder ob man besser davon die Finger lassen sollte.
Unter VCL benutze ich DataSets, aber unter FMX mit BindSourceDB ist irgendwie eine Sackgasse, und ich musste
bisher meine Datenschnittstelle von Hand bauen.

Ich möchte einfach einen Prototyp als Ersatz für LiveBindings hinbekommen, mit dem man solche Settings vereinfachen kann.
Der nächste Schritte wäre darüber eine rudimentäre Anbindung an DataSets zu bekommen.
Liebend gerne würde ich auch zu LiveBindings gehen, aber ich muss dann verlässliche Ergebnisse bekommen und
das UI muss sauber funktionieren.
Vielleicht gibt es in die Richtung ja auch Hinweise.

Das Ganze ist nur ein Test und Ergebnis einiger Versuche, also bitte erschlagt mich nicht gleich wenn
hier und da etwas nicht ganz Korrekt ist.

Rollo
Miniaturansicht angehängter Grafiken
clipboard01.jpg  
Angehängte Dateien
Dateityp: zip 01_BackgroundAdder.zip (677,2 KB, 6x aufgerufen)
  Mit Zitat antworten Zitat
Rollo62

Registriert seit: 15. Mär 2007
4.093 Beiträge
 
Delphi 12 Athens
 
#2

AW: [Fmx] BackgroundWorker Verwendung als TListView DataProvider

  Alt 12. Apr 2016, 07:28
Ich habe jetzt auch den Fetch synchronisiert
- Start Prepare (1 x synchronisiert)
- Fetch (n x syncronisiert für jeden einzelnen Record)
- Complete (1 x synchronisiert)

So läuft dann zumindest die ganze Bearbeitung im Hintergrund ab, ist aber natürlich etwas langsamer.

Hat denn jemand einen Kommentar dazu, ob soetwas sinnvoll ist ?
Wahrscheinlich müsste man noch trennen in Fetch (könnte im Background-Thread) und
setze Item im UI-Thread, um etwas zu Beschleunigen.

Das Problem ist aber anscheinend schon das Bitmap, weil es immer einen UI-Thread vorraussetzt (oder nicht ??).
Ich vermute mal das es immer mit einem Canvas zusammenarbeitet, und man bekommt es nicht nur als
bytes Array zum Speichern geliefert.

Gibt es irgendwelche Ideen wie man das noch besser/sicherer in den Hintergrund bekommen könnte ?

Rollo
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 12:17 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz