|
Antwort |
Registriert seit: 15. Mär 2007 4.116 Beiträge Delphi 12 Athens |
#1
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:
In dem ListView_Worker bearbeite ich die Prozeduren in verschiedenen Steps, und synchronisiere die ListView relevanten Zugriffe, wenn nötig.// ... 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 );
Delphi-Quellcode:
Als Beispiel lade ich Texte und Bilder, und zeige bei der Verarbeitung einen Animator an.
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; - 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:
Oder ob man die Variable mitführen und von Hand freigeben muss ?
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; 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 |
Zitat |
Registriert seit: 15. Mär 2007 4.116 Beiträge Delphi 12 Athens |
#2
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 |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs 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
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |