2012-05-18 11 views
8

Wiem, że muszę zadzwonić Synchronizuj, aby zaktualizować plik vcl z wątku, który nie utworzył formantów lub wysłać wiadomość do okna.Aktualizacja VCL z tego samego wątku, który utworzył interfejs użytkownika. Czemu?

Często słyszałam słowo nie wątkowo bezpieczne, ale nie mogę znaleźć prawdziwego wyjaśnienia na temat tego, co się dzieje.

Wiem, że aplikacja może ulec awarii z naruszeniem dostępu, ale znowu nie wiem, dlaczego?

Proszę rzucić światło na ten temat.

+3

Było trochę wyjaśnienia w [docs] (http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/devwin32/threadsusingthemainvclthread_xml.html). Jakoś nie mogę znaleźć czegoś podobnego w obecnej [dokumentacji] (http://docwiki.embarcadero.com/RADStudio/en/How_To_Build_Multithreaded_Applications). –

Odpowiedz

8

Informacje o bezpieczeństwie wątków GDI w systemie Windows, see this reference article.

To wyraźnie wskazuje, że może dostęp bezpiecznie uchwyty z wielu wątków, ale, że nie powinno być wykonane w tym samym czasie. Musisz chronić dostęp do uchwytów GDI, np. używanie krytycznych sekcji.

Pamiętaj, że GDI uchwyty, podobnie jak większość obsługuje Windows są wskaźniki struktur wewnętrznych odwzorowanych do integer (NativeUInt pod nowszym Windows dla 64-bitowej kompatybilności). Podobnie jak w przypadku wielu wątków, równoczesny dostęp do tej samej treści może być źródłem problemów, które są bardzo trudne do zidentyfikowania i naprawienia.

Część interfejsu użytkownika samego VCL nigdy nie miała być bezpieczna dla wątków od samego początku, ponieważ opierała się na nie-wątkowym Windows API. Na przykład, jeśli zwolnisz obiekt GDI w wątku, który nadal jest potrzebny w innym wątku, staniesz przed potencjalnym GPF.

Embarcadero (w tym czasie) mógł utworzyć wątek bezpieczny dla VCL, szeregując dostęp do wszystkich interfejsów przez krytyczne sekcje, ale może mieć dodatkową złożoność i zmniejszyć ogólną wydajność. Zauważ, że nawet platforma Microsoft .Net (zarówno w wersji WinForms, jak i WPF) wymaga również dedykowanego wątku do dostępu do interfejsu użytkownika, AFAIK.

Tak więc, aby odświeżyć interfejs z wielu wątków, masz kilka wzorów:

  1. Zastosowanie Synchronize połączenia z gwintem;
  2. Wysyła niestandardową wiadomość GDI (patrz WM_USER) z wątków działających w tle, aby powiadomić wątek interfejsu użytkownika, że ​​potrzebne jest odświeżenie;
  3. Podejście bezpaństwowe: interfejs będzie odświeżał zawartość od czasu do czasu, z poziomu logiki (za pomocą licznika czasu lub po naciśnięciu niektórych przycisków, które mogą zmienić dane).

Z mojego punktu widzenia preferuję opcję 2 dla większości interfejsów użytkownika i dodatkową opcję 3 (którą można łączyć z opcją 2) w celu zdalnego dostępu klienta do serwera. Dlatego nie musisz chcieć od strony serwera, aby wywołać pewne zdarzenie aktualizacji w interfejsie użytkownika. W świecie HTTP/AJAX RESTful ma to zdecydowanie sens. Opcja 1 jest nieco powolna, IMHO.We wszystkich przypadkach opcje 2 i 3 oczekują wyraźnego n-Tier layered architecture, w którym logika i interfejs użytkownika nie są mieszane, ale mimo to jest to dobry wzór do naśladowania dla każdego poważnego rozwoju.

+2

"Jasno stwierdza, że ​​można uzyskać dostęp do bezpiecznych uchwytów z wielu wątków, ale nie należy ich wykonywać w tym samym czasie." Tego nie mówi. Dopóki twój dostęp jest tylko do odczytu, wiele wątków może jednocześnie uzyskać dostęp do GDI, właściwości okna itd. Mówiąc o bezpieczeństwie wątku, nie ma sensu być nieprecyzyjnym. Termin wątek bezpieczny sam w sobie nie ma znaczenia, jak zauważył Eric Lippert: http://blogs.msdn.com/b/ericlippert/archive/2009/10/19/what-is-this-thing-you-call -thread-safe.aspx –

+2

Masz rację, z technicznego POV.Wziąłem pod uwagę fakt, że dostęp do uchwytu GDI polega głównie na wysyłaniu do niego wiadomości oraz potencjalnym alokowaniu i uwalnianiu powiązanych zasobów (czcionka, długopis ...). Moja rada/zasada została podjęta w celu zapewnienia bezpieczeństwa dostępu poprzez zniechęcanie do równoczesnego dostępu, w szczególności za pośrednictwem warstwy VCL. Wysyłanie wiadomości do instancji GDI * jest * bezpieczne dla wątków, ale przydzielanie/zwalnianie zasobów GDI ORAZ ustawianie parametrów nie jest - tak zostało napisane. Można mieszać polecenia GDI, miksując komunikaty, i może to mieć nieoczekiwane działanie. Tak więc "jeden na raz". –

+1

Zobacz także komentarz Remy tutaj: [Co tak naprawdę oznacza VCL to niciafeafe?] (Http://borland.newsgroups.archived.at/public.delphi.vcl.components.using.win32/200610/0610063192.html) . –

4

Sterowanie Windows za pomocą uchwytów nie jest bezpieczne dla wątków (tj. Nie można do nich bezpiecznie przejść przez dwa różne wątki w tym samym czasie), a Delphi zawija elementy sterujące systemu Windows w celu uzyskania kontrolek VCL. Ponieważ kontrola jest dostępna przez główny wątek GUI, musisz pozostawić je w spokoju, jeśli wykonujesz inny wątek.

+2

Jest to bardziej złożone niż to. Ogólnie Windows API jest bezpieczny dla wątków. Właściwości okna można odczytać z wielu wątków jednocześnie. Modyfikacje powinny ograniczać się do wątku, z którym okno ma powinowactwo. –

+1

@ David, tak, jestem pewien, że to prawda, ale spójrzmy prawdzie w oczy, najprostszym sposobem jest założenie tylko dostępu do pojedynczego wątku. To, co można zrobić i co jest praktyczne, to często dwie różne rzeczy ;-) – Misha

12

Jedną z największych przyczyn braku bezpieczeństwa nici w kontrolkach interfejsu użytkownika VCL jest TWinControl.Handle getter właściwości. Nie jest to zwykły dostęp tylko do odczytu kontrolki HWND. Tworzy także HWND, jeśli jeszcze nie istnieje. Jeśli wątek roboczy odczytuje właściwość Handle, gdy nie istnieje jeszcze HWND, tworzy nową HWND w kontekście wątku roboczego, która jest niepoprawna, ponieważ HWND s są powiązane z kontekstem tworzenia wątku, co spowodowałoby, że kontrola własna w najlepszym wypadku przestałaby działać ponieważ komunikaty systemu Windows dla kontrolki nie będą już przechodzić przez główną pętlę komunikatów. Ale gorzej, jeśli wątek główny odczytuje tę samą właściwość Handle w tym samym czasie, co wątek roboczy (na przykład, jeśli wątek główny dynamicznie odtwarza Handle z wielu powodów), istnieje warunek wyścigowy, między którym tworzy się kontekst wątku HWND, który zostanie przypisany jako nowy Handle, a także potencjalny potencjał wycieku rączki, jeśli oba wątki zakończą się tworzeniem nowych HWND s, ale tylko jedna może być przechowywana, a druga wycieknie.

Innym sprawca nitki unsafety jest MakeObjectInstance() funkcja VCL, która VCL się wewnętrznie do przydzielania TWinControl.WndProc() niż statyczne metody klasy jak procedura komunikatów okna TWinControl.Handle, a także przypisanie jakichkolwiek TWndMethod -typed sposób obiektu jako procedura komunikatu funkcji HWND utworzona przez funkcję AllocateHWnd() (używana na przykład przez TTimer). MakeObjectInstance() zajmuje dużo miejsca na przydzielanie/buforowanie i twidowanie pamięci, która nie jest chroniona przed współbieżnym dostępem przez wiele wątków.

Jeśli można zapewnić przeznaczono kontrola na Handle z wyprzedzeniem, a jeśli można zapewnić główny wątek nie odtwarza że Handle natomiast gwint pracownik pracuje, to możliwe bezpiecznie wysyłać wiadomości do tej kontroli z wątku roboczego bez używania Synchronize(). Ale nie jest to wskazane, jest zbyt wiele czynników, które wątek roboczy musiałby wziąć pod uwagę. Dlatego najlepiej jest uzyskać dostęp do interfejsu użytkownika w głównym wątku. W ten sposób ma być używany system VCL UI.

Powiązane problemy