Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Unterschied TTread.Synchronize / TThread.Queue (https://www.delphipraxis.net/185929-unterschied-ttread-synchronize-tthread-queue.html)

Harry Stahl 19. Jul 2015 13:11

Unterschied TTread.Synchronize / TThread.Queue
 
Hallo,

ich habe gerade male eine Verständnisfrage zur Verwendung von TThread.Synchronize und TThread.Queue.

Beide proceduren verwendet man ja im Prinzip, um von einem Thread außerhalb des MainThreads auf Daten bzw. GUI-Elemente des Hauptthreads zuzugreifen.

Während ich bei der Verwendung der beiden Proceduren bislang in TTask keinen großen Unterschied bemerkt habe, frage ich mich gerade, warum in dem folgenden TParallel-For Beispiel der Aufruf mit Queue funktioniert, der mit Synchronize sich aber aufhängt:

Delphi-Quellcode:
procedure TForm14.Button1Click(Sender: TObject);
var
  L: Integer;
begin
  Listbox1.Clear;

  TParallel.For (1,10, procedure (i:Integer)
  begin
    //TThread.Synchronize(NIL, //--> funktioniert nicht, Programm hängt
    TThread.Queue(NIL, // das geht
    procedure
    begin
      ListBox1.Items.Add( 'Nummer' + i.tostring);
    end);
  end);
end;
Mir ist durchaus bekannt, dass Synchronize eine "blocking" Methode ist, "Queue" aber nicht, dennoch hätte ich angenommen, dass die Abarbeitung grundsätzlich hier auch mit Synchronize funktionieren würde. Tut es aber nicht.

Hat jemand eine Erklärung dafür?

jaenicke 19. Jul 2015 13:51

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Du bleibst ja während der Ausführung der einzelnen Threads im Hauptthread im Aufruf von TParallel.For. Deshalb können die anderen Threads in dieser Zeit auch nicht mit dem Hauptthread synchronisieren, da der gleichzeitig auf die einzelnen Schleifenthreads wartet.

Das ist ein klassischer Deadlock.

Harry Stahl 19. Jul 2015 15:04

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Verstehe ich das richtig, dass TParallel.for dann so eine Art Mischung zwischen Ablauf im Hauptthread und eigener Thread ist?

Im Gegensatz zu TTask, was ja wirklich ein eigener Thread ist, daher kann man dort beides (also Queue oder Synchronize) verwenden?

Uwe Raabe 19. Jul 2015 15:49

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Du kannst das in deinem Beispiel sehen, wenn du nach dem TParallel.For einfach noch eine Zeile

Delphi-Quellcode:
  ListBox1.Items.Add('Ende');
einfügst.

Sir Rufo 19. Jul 2015 16:04

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Das
Delphi-Quellcode:
TParallel.&For()
ist wie ein
Delphi-Quellcode:
LTask := Task.Run( ... );
LTask.Wait();
Und dort würde auch ein Synchronize einen Deadlock erzeugen.

Harry Stahl 19. Jul 2015 18:30

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Zitat:

Zitat von Sir Rufo (Beitrag 1309179)
Das
Delphi-Quellcode:
TParallel.&For()
ist wie ein
Delphi-Quellcode:
LTask := Task.Run( ... );
LTask.Wait();
Und dort würde auch ein Synchronize einen Deadlock erzeugen.

Aber nur wegen des LTask.Wait.

Zitat:

Zitat von Uwe Raabe (Beitrag 1309178)
Du kannst das in deinem Beispiel sehen, wenn du nach dem TParallel.For einfach noch eine Zeile

Delphi-Quellcode:
  ListBox1.Items.Add('Ende');
einfügst.

Das wird dann als erstes in die Liste eingefügt und erst später die Einträge aus dem Parallel.Loop. Daraus kann ich dann folgern, dass ich die ganze Zeit noch im Mainthread war, oder?

Aber grundsätzlich Danke, ich glaube, ich komme der Sache vom Verständnis her näher.

jaenicke 19. Jul 2015 20:43

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Zitat:

Zitat von Harry Stahl (Beitrag 1309189)
Zitat:

Zitat von Uwe Raabe (Beitrag 1309178)
Du kannst das in deinem Beispiel sehen, wenn du nach dem TParallel.For einfach noch eine Zeile

Delphi-Quellcode:
  ListBox1.Items.Add('Ende');
einfügst.

Das wird dann als erstes in die Liste eingefügt und erst später die Einträge aus dem Parallel.Loop. Daraus kann ich dann folgern, dass ich die ganze Zeit noch im Mainthread war, oder?

Wenn du Queue benutzt, werden die Threadinhalte tatsächlich erst hinterher ausgeführt. Das liegt aber nicht daran, dass die Schleifeninhalte hinterher ausgeführt werden, sondern an der Auskoppelung durch Queue.

Das kannst du sehr gut an diesem Beispiel sehen:
Delphi-Quellcode:
  MessageBox(0, 'Vorher', 'Test', 0);
  TParallel.For (1,2, procedure (i:Integer)
  begin
    MessageBox(0, PChar(i.ToString), 'Test', 0);
  end);
  MessageBox(0, 'Nachher', 'Test', 0);
Du wirst sehen, dass die Messagebox "Nachher" erst angezeigt wird, wenn die beiden anderen bestätigt sind. Das kannst du aber ja auch einfach mit einem Haltepunkt sehen.

Sir Rufo 19. Jul 2015 22:00

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Da in
Delphi-Quellcode:
TParallel.&For
eben genau dieses
Delphi-Quellcode:
Wait
gemacht wird, muss/kann man es damit vergleichen.

Rollo62 20. Jul 2015 19:35

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Aber in TParallel laufen doch im besten Fall mehrere Threads parallel ab (hoffentlich).
Und das TaskWait kommt für alle am Ende, oder nicht ?

Wie sollte man das den jetzt aufbauen damit es threadsafe wird, geht das überhaupt ?

Die Strings z.B. in TDictionary o.ä zwischenspeichern, und dann nach der TParallel Schleife in die
Listbox übertragen ?
Oder die gute alte TStringList ?
Dann würden zwar die Berechnungen parallel laufen, aber die Übertragung in die Listbox wieder im Hauptthread.

Dann wäre doch das TParallel in solchen Fällen nicht besonders sinnvoll, nur da wo es keine
Inputs zu Komponenten im Hauptthread geben muss.

Oder gibt es eine Konstellation wie man das auch mit TListBox parallel hinbekommt ?

Rollo

jaenicke 20. Jul 2015 21:04

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Zitat:

Zitat von Rollo62 (Beitrag 1309314)
Dann wäre doch das TParallel in solchen Fällen nicht besonders sinnvoll, nur da wo es keine
Inputs zu Komponenten im Hauptthread geben muss.

TParallel ist nur für die Daten- und Logikteile der Anwendung gedacht, nicht für die GUI, ja. Ehrlich gesagt habe ich es da aber auch nie vermisst, da im Hauptthread ohnehin keine längeren Operationen laufen sollten. Und der Teil, der TParallel benutzt, ist ja per Definition eine solche längere Operation, sonst bräuchte man ja keine Threads.

Es sollte also in dem Fall einfach ein Thread für die ursprüngliche Verarbeitung gestartet werden und schon ist das Problem gelöst.

Rollo62 21. Jul 2015 10:01

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Hallo jaenicke,

ja schon klar.

Aber ich denke es geht Harry hier um das schnelle Befüllen einer ListBox / ListView.

Wie wäre es wenn man die maximale Anzahl leerer Zeilen zuvor anlegt, und dann mit Parallel schnell füllt,
so das sich jeder Thread nur um seinen Teil der ListBox Einträge kümmern muss.

Dazu müsste dann gewartet werden, und die ListBox natürlich mit Begin- EndUpdate gekapselt werden,
aber das könnte doch das Befüllen beschleunigen, wenn das ListBox Design so etwas zulässt.

Mir ist auch nicht ganz wohl dabei selbst wenn das gehen würde, weil man ja immer in der nächsten Version eine
interne Änderung in der Listbox haben könnte die dann auf einmal Crasht.

Ich würde wohl versuchen so etwas wie eine virtuelle ListBox (als virtualTree) zu bauen, die nur den sichtbaren Teil jeweils nachlädt,
aber ich würde auch erstmal darüber nachdenken warum die StandardListbox das nicht auch kann.

Vielleicht geht es ja doch irgendwie ?

Rollo

jaenicke 21. Jul 2015 10:15

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Ich selbst habe dafür die nötigen Fenster rein über die API erstellt. Dann sind die nämlich threadsicher und ich kann von mehreren Threads aus problemlos über Events auf die GUI zugreifen ohne diese zu synchronisieren.
Allerdings ist das natürlich nicht gerade der Weg, den man in Delphi gehen möchte...

Wenn es um eine Listenanzeige geht, benutze ich dafür in der Tat die VirtualStringTree. Dann aktualisiere ich gesichert per TMonitor die Daten und löse einfach ein Neuzeichnen aus.

Uwe Raabe 21. Jul 2015 10:21

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Zitat:

Zitat von jaenicke (Beitrag 1309356)
Ich selbst habe dafür die nötigen Fenster rein über die API erstellt. Dann sind die nämlich threadsicher und ich kann von mehreren Threads aus problemlos über Events auf die GUI zugreifen ohne diese zu synchronisieren.
Allerdings ist das natürlich nicht gerade der Weg, den man in Delphi gehen möchte...

Insbesondere, wenn man das Ganze unter FMX aufzieht um verschiedene Zielplattformen anzusprechen.

Harry Stahl 21. Jul 2015 17:13

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Zitat:

Zitat von Rollo62 (Beitrag 1309353)
Hallo jaenicke,

ja schon klar.

Aber ich denke es geht Harry hier um das schnelle Befüllen einer ListBox / ListView.
Rollo

Nö, das war nur ein Beispiel mit dem ich ein wenig "gespielt" hatte, um ein mögliches Update der GUI zu prüfen.

Aber ich habe jetzt verstanden, dass man TParallel-For (und auch TTask) im Prinzip in der Hauptsache verwendet, um Prozesse in der Summe schneller bearbeitet zu bekommen (indem eben mehrere Prozessor-Kerne benutzt werden, wenn sie denn zur Verfügung stehen).

Der bisherige "normale" zusätzliche Thread, der Hintergrundarbeit durchführt und hin und wieder mal die GUI aktualisiert scheint mir daher durchaus weiterhin seine Berechtigung zu haben. Oder?

jaenicke 21. Jul 2015 18:08

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Zitat:

Zitat von Harry Stahl (Beitrag 1309415)
Der bisherige "normale" zusätzliche Thread, der Hintergrundarbeit durchführt und hin und wieder mal die GUI aktualisiert scheint mir daher durchaus weiterhin seine Berechtigung zu haben. Oder?

Ja, sicher, TParallel ist nur ein kleines Hilfsmittel für kleine Aufgaben um die leichter in Threads zu packen, aber für umfangreichere Aufgaben lohnt sich eher eine Umsetzung mit eigenen Threads oder Tasks.

Stevie 21. Jul 2015 18:18

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Seit der PPL denken plötzlich alle, Multithreading und parallel programming sei nen Kindergeburtstag :)

TParallel.For ist blockend, d.h. alles, was zurück in diesen Thread synchronisieren will, sorgt für einen Deadlock.

Wenn du innerhalb der Tasks eines parallel for die GUI updaten willst, dann musst du die parallel for in einem eigenen Thread/Task starten.

Hier mal nen kleines Nonsense Beispiel:

Delphi-Quellcode:
  TTask.Run(
    procedure
    begin
      TParallel.&For(0, 1000,
        procedure (i: Integer)
        begin
          Sleep(50);
          TThread.Queue(nil,
            procedure
            begin
              Caption := IntToStr(i);
            end);
        end);
      TThread.Queue(nil,
        procedure
        begin
          ShowMessage('done');
        end);
    end);
  ShowMessage('started');

Uwe Raabe 21. Jul 2015 19:01

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Zitat:

Zitat von Stevie (Beitrag 1309417)
Seit der PPL denken plötzlich alle, Multithreading und parallel programming sei nen Kindergeburtstag :)

Aus den Slides meines Vortrags auf den Delphi-Tagen 2013:

Zitat:

Zitat von Danny Thorpe
New programmers are drawn to multithreading like moths to flame, with similar results.


Mavarik 21. Jul 2015 19:15

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Zitat:

Zitat von jaenicke (Beitrag 1309416)
Ja, sicher, TParallel ist nur ein kleines Hilfsmittel für kleine Aufgaben um die leichter in Threads zu packen, aber für umfangreichere Aufgaben lohnt sich eher eine Umsetzung mit eigenen Threads oder Tasks.

Nö wieso das den?

Klar sollte man je nach Aufgabe wählen...

Ich habe "alle" Arten in meiner Software...

- Herkömmliche Thread's für schnelle Reaktionen... Die werden am Anfang des Programmstarts erzeugt und warten nur noch auf ein Event... Daher startet die Verarbeitung in wenigen Nano-Sekunden (Kein Overhead)

- TTask.Run... für das "schnelle mal zwischendurch" zum Beispiel "Save Button"... Der Datenbankzugriff kommt in die anonyme procedure - hier ist der Overhead egal. Ob die Datenbank einige ms später geschrieben wird oder nicht ist egal...

- TParallel... den Overhead für den Thread-Pool gönne ich mir nur dann, wenn ich NIX Synchronisieren muss und die einzelne Verarbeitung länger dauert. Außerdem habe ich die automatische Anpassung an die Kerne... Muss mir also nicht überlegen, wie viele Worker Threads ich erzeugen darf...

- (Idleworker) ist zwar kein Thread aber von System her gehört er für mich hier in die Liste... Nach dem Motto UI-Thread Queue - wenn Du Zeit dafür hast.

Und schon wird die Thread Anwendung zum Kindergeburtstag... 8-)

Mavarik

Rollo62 21. Jul 2015 20:19

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Hallo Mavarik,

ja das Macht Sinn.

TParallel hatte ich mir noch nicht so tief angeschaut, hatte wohl eine falsche Vorstellung davon.
Ansonsten benutze ich TTask.Run schon recht häufig, für Parallel war noch keine echte Not.

Weil das so simple und schnell mit der anonymen Prozedur vonstatten geht ist das eine echte Beschleunigung, statt erst eigene Threads zu programmieren.
Das ist mein Favorit :-)

Mit TIdleWorker habe ich mich auch seit kurzem beschäftigt, damit kann man auch die Aufgaben schön entkoppeln.

Es macht halt alles Sinn, man sollte nur wissen was geht und was nicht, daran hapert es meistens.

Rollo

jaenicke 21. Jul 2015 21:16

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Zitat:

Zitat von Rollo62 (Beitrag 1309428)
Weil das so simple und schnell mit der anonymen Prozedur vonstatten geht ist das eine echte Beschleunigung, statt erst eigene Threads zu programmieren.

TThread.CreateAnonymousThread gibt es allerdings auch noch. ;-)

Mavarik 22. Jul 2015 09:42

AW: Unterschied TTread.Synchronize / TThread.Queue
 
Zitat:

Zitat von jaenicke (Beitrag 1309433)
Zitat:

Zitat von Rollo62 (Beitrag 1309428)
Weil das so simple und schnell mit der anonymen Prozedur vonstatten geht ist das eine echte Beschleunigung, statt erst eigene Threads zu programmieren.

TThread.CreateAnonymousThread gibt es allerdings auch noch. ;-)

Stimmt - das fehlte in meiner Liste...


Alle Zeitangaben in WEZ +1. Es ist jetzt 20:10 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