Wie funktioniert die asynchrone Programmierung in Delphi?

8

Ich habe eine Anwendung, bei der die meisten Aktionen einige Zeit in Anspruch nehmen und ich möchte die Benutzeroberfläche jederzeit ansprechbar halten. Das Grundmuster einer vom Benutzer ausgelösten Aktion lautet wie folgt:

  1. Bereite die Aktion vor (im Hauptthread)
  2. Führe die Aktion aus (in einem Hintergrund-Thread, während die GUI-Antwort beibehalten wird)
  3. zeigt die Ergebnisse (im Hauptthread) an

Ich habe mehrere Dinge versucht, um dies zu erreichen, aber alle verursachen auf lange Sicht Probleme (scheinbar zufällige Zugriffsverletzungen in bestimmten Situationen).

  1. Bereiten Sie die Aktion vor, rufen Sie dann einen Hintergrundthread auf und verwenden Sie am Ende des Hintergrundthreads Synchronize , um ein OnFinish -Ereignis im Hauptthread aufzurufen.
  2. Bereiten Sie die Aktion vor, rufen Sie dann einen Hintergrundthread auf und verwenden Sie am Ende des Hintergrundthreads PostMessage , um den GUI-Thread darüber zu informieren, dass die Ergebnisse fertig sind.
  3. Bereiten Sie die Aktion vor, rufen Sie dann einen Hintergrundthread und dann busy-wait auf (während Sie Application.ProcessMessages aufrufen), bis der Hintergrundthread beendet ist, und fahren Sie mit der Anzeige der Ergebnisse fort.

Ich kann mir keine andere Alternative ausdenken und nichts davon funktionierte perfekt für mich. Was ist der bevorzugte Weg, dies zu tun?

    
jpfollenius 14.09.2012, 12:50
quelle

3 Antworten

5

1) Ist der 'Orignal Delphi' Weg, zwingt den Hintergrund Thread zu warten, bis die synchronisierte Methode ausgeführt wurde und das System zu mehr Deadlock-Potential ausgesetzt ist, als ich glücklich bin. TThread.Synchronize wurde mindestens zweimal neu geschrieben. Ich habe es einmal auf D3 benutzt und hatte Probleme. Ich schaute auf, wie es funktionierte. Ich habe es nie wieder benutzt.

2) I das Design, das ich am häufigsten verwende. Ich benutze App-Lifetime-Threads (oder Thread-Pools), erstelle Inter-Thread-Comms-Objekte und stelle sie in Hintergrundthreads unter Verwendung einer Producer-Consumer-Warteschlange basierend auf einem TObjectQueue-Nachkommen ein. Der Hintergrundthread / die Hintergrundthreads bearbeiten die Daten / Methoden des Objekts, speichern Ergebnisse im Objekt und, wenn sie vollständig sind, PostMessage () das Objekt (in lParam umgewandelt) zurück zum Hauptthread für die GUI-Anzeige der Ergebnisse in einer Nachricht handler, (lade das lParam erneut zurück). Die Hintergrund-Threads im Haupt-GUI-Thread müssen dann niemals auf demselben Objekt operieren und müssen nie direkt auf Felder von einander zugreifen.

Ich verwende ein verstecktes Fenster des GUI-Threads (erstellt mit RegisterWindowClass und CreateWindow), für die Hintergrundthreads zu PostMessage zu, comms-Objekt in LParam und 'target' TwinControl (normalerweise eine TForm-Klasse), als WParam. Das triviale wndproc für das ausgeblendete Fenster verwendet TwinControl.Perform () nur, um das LParam an einen Nachrichtenhandler des Formulars weiterzugeben. Dies ist sicherer als PostMessaging des Objekts direkt an ein TForm.handle - das Handle kann sich leider ändern, wenn das Fenster neu erstellt wird. Das ausgeblendete Fenster ruft nie RecreateWindow () auf und daher ändert sich sein Handle nie.

Producer-Consumer Queues 'out from GUI', inter-thread comms classes / objects und PostMessage () 'in GUI' WERDEN gut funktionieren - ich mache das schon seit Jahrzehnten.

Die Wiederverwendung der Kommunikationsobjekte ist auch ziemlich einfach - erstellen Sie einfach eine Ladung in einer Schleife beim Start (vorzugsweise in einem Initialisierungsabschnitt, so dass die Kommunikationsobjekte alle Formulare überleben), und schieben Sie sie in eine PC-Warteschlange - das ist Ihre Schwimmbad. Es ist einfacher, wenn die comms-Klasse ein privates Feld für die Pool-Instanz hat - die Methode 'releaseBackToPool' benötigt dann keine Parameter und stellt sicher, dass die Objekte immer wieder in ihren eigenen Pool freigegeben werden, wenn mehr als ein Pool vorhanden ist.

3) David Heffermans Kommentar kann nicht wirklich verbessert werden. Mach es einfach nicht.

    
Martin James 14.09.2012, 13:48
quelle
5

Sie können das Muster in Frage stellen, indem Sie OTL verwenden, wie vom OTL-Autor hier

    
kludg 14.09.2012 13:32
quelle
1

Sie könnten Daten zwischen Threads als Nachrichten kommunizieren.

Thread1:

  1. Zuweisen von Speicher für eine Datenstruktur
  2. fülle es aus
  3. Senden Sie eine Nachricht an Thread2 mit dem Zeiger auf diese Struktur (Sie könnten entweder Windows-Nachrichten verwenden oder eine Warteschlange implementieren, um sicherzustellen, dass die Enque- und Dequeue-Methoden keine Racebedingungen haben)
  4. erhält möglicherweise eine Antwortnachricht von Thread2 ...

Faden 2:

  1. Empfangen Sie die Nachricht mit dem Zeiger auf die Datenstruktur von Thread1
  2. konsumiere die Daten
  3. den Speicher der Datenstruktur freigeben
  4. senden Sie möglicherweise eine Nachricht zurück an Thread1 auf ähnliche Weise (vielleicht die Datenstruktur wiederverwenden, aber Sie dann nicht freigeben)

Wenn Sie möchten, dass Ihre GUI nicht nur live ist, sondern auch auf einige Eingaben reagiert, erhalten Sie möglicherweise mehr als einen Thread ohne GUI, während die Eingabe verarbeitet wird, deren Verarbeitung lange dauert.

    
Alexey Frunze 14.09.2012 13:10
quelle