Aktualisierung der VCL von demselben Thread, der die Benutzeroberfläche erstellt hat. Warum?

8

Ich weiß, dass ich Synchronize aufrufen muss, um den vcl von einem Thread zu aktualisieren, der die Steuerelemente nicht erstellt hat, oder eine Nachricht an das Fenster senden.

Ich habe oft gehört, dass das Wort nicht sicher ist, aber ich kann keine wirkliche Erklärung dafür finden.

Ich weiß, dass die Anwendung mit einer Zugriffsverletzung abstürzen könnte, aber ich weiß auch nicht warum?

Bitte werfen Sie ein Licht auf dieses Thema.

    
opc0de 18.05.2012, 08:07
quelle

3 Antworten

8

Informationen zur GDI-Thread-Sicherheit in Windows finden Sie in dieser Referenz Artikel .

Es besagt eindeutig, dass Sie auf sichere Griffe aus mehreren Threads zugreifen können, aber dass es nicht gleichzeitig gemacht werden sollte. Sie müssen den Zugriff auf GDI-Handles schützen, z. Verwenden kritischer Abschnitte.

Denken Sie daran, dass GDI, wie die meisten Windows-Handles, Zeiger von internen Strukturen sind, die einem integer ( NativeUInt unter neueren Windows, für 64-Bit-Kompatibilität) zugeordnet sind. Wie immer bei Multithread-Computing kann der gleichzeitige Zugriff auf den gleichen Inhalt eine Quelle von Problemen sein, die sehr schwer zu identifizieren und zu beheben sind.

Der UI-Teil der VCL selbst war nie von Anfang an Thread-sicher, da er sich auf die nicht threadsichere Windows-API verließ. Wenn Sie beispielsweise ein GDI-Objekt in einem Thread freigeben, das in einem anderen Thread noch benötigt wird, stoßen Sie auf potentielles GPF.

Embarcadero (zu dieser Zeit) könnte die VCL threadsicher gemacht haben und alle UI-Zugriffe über kritische Abschnitte serialisiert haben, aber dies könnte die Komplexität erhöhen und die Gesamtleistung verringern. Beachten Sie, dass selbst die Microsoft .NET-Plattform (sowohl in WinForms als auch in WPF ) einen eigenen Thread für den UI-Zugriff erfordert, AFAIK.

Um die Benutzeroberfläche aus mehreren Threads zu aktualisieren, haben Sie mehrere Muster:

  1. Benutze Synchronize Aufrufe vom Thread;
  2. Senden Sie eine benutzerdefinierte GDI-Nachricht (siehe WM_USER ) aus den Hintergrundthreads, um den UI-Thread darüber zu informieren, dass eine Aktualisierung erforderlich ist.
  3. Einen statuslosen Ansatz verfolgen: Die Benutzeroberfläche aktualisiert von Zeit zu Zeit ihren Inhalt von der Logikebene (mithilfe eines Timers oder wenn Sie einige Schaltflächen drücken, die die Daten ändern können).

Aus meiner Sicht bevorzuge ich Option 2 für die meisten Benutzeroberflächen und eine zusätzliche Option 3 (die mit Option 2 gemischt werden kann) für den Remote-Client-Server-Zugriff. Daher müssen Sie nicht von der Serverseite möchten, dass ein Update-Ereignis für die Benutzeroberfläche ausgelöst wird. In einer HTTP / AJAX RESTvollen Welt macht dies definitiv Sinn. Option 1 ist etwas langsam, IMHO. In allen Fällen erwarten die Optionen 2 und 3 eine klare n-Tier-Schichtarchitektur , in der Logik und Benutzeroberfläche nicht gemischt sind: aber das ist ein gutes Muster, dem man folgen sollte, für jede ernste Entwicklung.

    
Arnaud Bouchez 18.05.2012, 12:19
quelle
12

Einer der Hauptgründe für die Thread-unsicherheit in den Steuerelementen der VCL-Benutzeroberfläche ist der% get-Wert für die Eigenschaft TWinControl.Handle . Es ist nicht nur ein einfacher schreibgeschützter Accessor des HWND des Steuerelements. Es erstellt auch das HWND , falls es noch nicht existiert. Wenn ein Worker-Thread die Handle -Eigenschaft liest, obwohl noch keine HWND existiert, erstellt er im Worker-Thread-Kontext eine neue HWND , was schlecht ist, weil HWND s an den erzeugenden Thread-Kontext gebunden sind, der rendern würde Die Eigentümersteuerung ist bestenfalls ziemlich inoperabel, da Windows-Nachrichten für die Steuerung nicht mehr durch die Hauptnachrichtenschleife gehen würden. Aber schlimmer, wenn der Haupt-Thread die gleiche Handle -Eigenschaft gleichzeitig mit dem Worker-Thread liest (zum Beispiel, wenn der Haupt-Thread die Handle aus verschiedenen Gründen dynamisch neu erstellt), gibt es eine Race-Bedingung zwischen Der Threadkontext erstellt das HWND , das als neues Handle zugewiesen wird, sowie ein potenzielles Handle-Leak-Potenzial, wenn beide Threads am Ende HWND s erstellen, aber nur eines beibehalten werden kann und das andere leckt.

Ein weiterer Verursacher der thread-unsafety ist die MakeObjectInstance() -Funktion der VCL, die die VCL intern benutzt, um die TWinControl.WndProc() nicht-statische Klassenmethode als Nachrichtenprozedur des TWinControl.Handle -Fensters zuzuweisen sowie TWndMethod -typed-Objektmethode als Nachrichtenprozedur der HWND , die von der AllocateHWnd() -Funktion erstellt wurde (z. B. von TTimer ). MakeObjectInstance() macht ziemlich viel Speicherzuweisung / Caching und Twiddeln dieses Speicherinhalts, die nicht vor gleichzeitigem Zugriff durch mehrere Threads geschützt sind.

Wenn Sie sicherstellen können, dass die Handle eines Steuerelements vorzeitig zugewiesen wird, und if können Sie sicherstellen, dass der Haupt-Thread dieses Handle während des Worker-Threads nicht erneut erstellt läuft, dann ist es möglich , sicher Nachrichten aus dem Arbeitsthread an dieses Steuerelement zu senden, ohne Synchronize() zu verwenden. Aber es ist nicht ratsam, es gibt einfach zu viele Faktoren, die der Arbeiter-Thread berücksichtigen müsste. Aus diesem Grund ist es am besten, dass der all UI-Zugriff nur im Hauptthread erfolgt. So soll das VCL-UI-System verwendet werden.

    
Remy Lebeau 18.05.2012 15:16
quelle
4

Windows-Steuerelemente mit Handles sind nicht Thread-sicher (d. h. sie können nicht gleichzeitig von zwei verschiedenen Threads sicher zugegriffen werden), und Delphi umschließt die Windows-Steuerelemente, um Ihnen die VCL-Steuerelemente zu geben. Da auf die Steuerelemente vom Haupt-GUI-Thread zugegriffen wird, müssen Sie sie in Ruhe lassen, wenn Sie einen anderen Thread ausführen.

    
Misha 18.05.2012 09:17
quelle