2011-04-08 33 views
11

Aplikacja jest napisana w Delphi XE.Synchronizacja/wysyłanie danych między wątkami

Mam dwie klasy, TBoss i TWorker, które są oparte na TThread. TBoss to pojedynczy wątek, który uruchamia, a następnie utworzy około 20 wątków TWorker.

Kiedy boss tworzy instancję TWorkera, przypisuje mu metodę wywoływania synchronizacji, kiedy Pracownik skończył z tym, co robi, nazywa tę metodę, która pozwala Szefowi uzyskać dostęp do rekordu pracownika.

Jednak czuję, że to jest problem, wywołanie synchronizacji wydaje się blokować całą aplikację - blokowanie głównego wątku (ui). Naprawdę powinien po prostu zsynchronizować tego pracownika z wątkiem szefa ....

Wcześniej używałam wiadomości/spakowanych rekordów do wysyłania treści między wątkami, które działały dobrze. Jednak robienie tego w ten sposób jest o wiele czystsze i ładniejsze ... po prostu bardzo blokujące.

Czy istnieje sposób na wywołanie Syncronize w pracownikach, aby tylko czekać na wątek szefa?

Mój kod:

type 
     TWorker = class(TThread) 
     private 
     fResult : TResultRecord; 
     procedure SetOnSendResult(const Value: TNotifyEvent); 
     .... 
     .... 
     public 
     property OnSendResult: TNotifyEvent write SetOnSendResult; 
     property Result : TResultRecord read fResult; 
     .... 
    end; 

    ... 
    ... 
    procedure TWorker.SendBossResults; 
    begin 
     if (Terminated = False) then 
     begin 
     Synchronize(SendResult); 
     end; 
    end; 

    procedure TWorker.SendResult; 
    begin 
     if (Terminated = false) and Assigned(FOnSendResult) then 
     begin 
     FOnSendResult(Self); 
     end; 
    end; 

Wtedy w moim wątku Boss zrobię coś takiego

var 
     Worker : TWorker; 
    begin 
     Worker    := TWorker.Create; 
     Worker.OnTerminate := OnWorkerThreadTerminate; 
     Worker.OnSendResult := ProcessWorkerResults; 

Więc mój szef ma wówczas metoda nazywa się ProcessWorkerResults - to co zostaje uruchomiony na Synchronizuj (SendResult); pracownika.

procedure TBoss.ProcessWorkerResults(Sender: TObject); 
    begin 
     if terminated = false then 
     begin 
     If TWorker(Sender).Result.HasRecord then 
     begin 
      fResults.Add(TWorker(Sender).Result.Items); 
     end; 
     end; 
    end; 
+1

Która wersja Delphi jest (jest kilka nowych opcji od 2009)? – mjn

+0

Delphi XE - dziękuję – Wizzard

+1

Twierdzę, że używanie komunikatów do przekazywania danych między wątkami, które nie wymaga mechanizmu "synchronizacji" aplikacji (jest w "strukturze" wątków), jest dużo bardziej eleganckie niż używanie prymitywów synchronizacji wokół współdzielonych danych. – Misha

Odpowiedz

9

Synchronize jest specjalnie zaprojektowany do wykonywania kodu w wątku główny; dlatego wydaje się, że wszystko blokuje.

można używać na kilka sposobów komunikowania się z wątków roboczych do wątku Boss:

  • Dodaj wywołania zwrotnego do każdego wątku roboczego, i przypisać go z wątku boss gdy jest tworzony. Może on cofać się niezależnie od parametrów, wraz z identyfikatorem wątku lub innym identyfikatorem.

  • Opublikuj wiadomość z wątku roboczego do wątku głównego za pomocą PostThreadMessage. Wadą tego jest to, że wątek musi mieć klamkę okienną (patrz Classes.AllocateHWnd w pomocy Delphi i komentarz Davida Heffernana poniżej).

  • Użyj dobrej jakości biblioteki wątku innej firmy niż . Zobacz OmniThreadLibrary - to nic nie kosztuje, OS, i bardzo dobrze napisane.

Mój wybór byłby trzeci. Primoz wykonał dla ciebie ciężką pracę. :)

Po twoim komentarzu, oto coś na wzór mojej pierwszej sugestii. Zauważ, że jest to untested, ponieważ pisanie kodu dla wątku TBoss i TWorker + aplikacja testowa jest trochę długa na czas mam prawo w tej chwili ... Powinno wystarczyć, aby podać ci istotę, mam nadzieję.

type 
    TWorker = class(TThread) 
    private 
    fResult : TResultRecord; 
    fListIndex: Integer; 
    procedure SetOnSendResult(const Value: TNotifyEvent); 
    .... 
    .... 
    public 
    property OnSendResult: TNotifyEvent write SetOnSendResult; 
    property Result : TResultRecord read fResult; 
    property ListIndex: Integer read FListIndex write FListIndex; 
    .... 
    end; 

type 
    TBoss=class(TThread) 
    private 
    FWorkerList: TThreadList; // Create in TBoss.Create, free in TBoss.Free 
    ... 
    end; 

procedure TWorker.SendBossResults; 
begin 
    if not Terminated then 
    SendResult; 
end; 

procedure TBoss.ProcessWorkerResults(Sender: TObject); 
var 
    i: Integer; 
begin 
    if not terminated then 
    begin 
    If TWorker(Sender).Result.HasRecord then 
    begin 
     FWorkerList.LockList; 
     try 
     i := TWorker(Sender).ListIndex; 
     // Update the appropriate record in the WorkerList 
     TResultRecord(FWorkerList[i]).Whatever... 
     finally 
     FWorkerList.UnlockList; 
     end; 
    end; 
    end; 
end; 
+0

Należy zachować ostrożność w Classes.AllocateHWnd, który nie jest bezpieczny dla wątków. Musisz serializować połączenia z AllocateHWnd i DeallocateHWnd. Nie mam pojęcia, dlaczego VCL tego nie robi iw moim kodzie chwytam te procedury, aby osiągnąć bezpieczeństwo wątków. –

+0

@ David: Wiedziałem o tym. :(Powinienem wspomnieć o tym, że to kolejna wada: dzięki za poprawkę –

+0

Cześć, tak, wydaje mi się, że to właśnie jest przyczyną mojego problemu, ale twoim pierwszym rozwiązaniem jest "Dodaj wywołanie zwrotne do każdego wątku roboczego", co jest w zasadzie Zrobiłem, więc jak to się różni? – Wizzard

8

Można użyć kolejki wątków bezpiecznych. W DelphiXE znajduje się TThreadedQueue. Jeśli nie masz DXE, spróbuj OmniThreadLibray - ta biblioteka jest bardzo dobra dla wszystkich problemów z wątkami.

+0

TThreadedQueue ma problemy w środowisku wielowątkowym z wieloma konsumentami lub wieloma producentami. Zamiast tego użyj bardziej lekkiej kolejki. Zobacz [link] (http://www.pascalgamedevelopment.com/showthread.php?4961-freepascal-Delphi-thread-safe-queue) –

+0

Nie jestem pewien, w jaki sposób odpowiada na pytanie "Czy istnieje sposób na wywołanie Syncronize w pracownikach, aby tylko czekać na wątek szefa?" - Możesz wytłumaczyć? –

1

Jak już wspomniano nowe opcje w Delphi 2009 i wyżej, tu jest link do przykładu komunikacji Producent/Konsumentów pomiędzy wątkami, na podstawie nowych zamków objct moim blogu:

Thread Synchronization with Guarded Blocks in Delphi

W note regarding the deprecated methods TThread.Suspend i TThread.Resume, Embarcadero DocWiki Delphi zaleca „gwint techniki synchronizacji powinny być na podstawie SyncObjs.TEvent i SyncObjs.TMutex. "Istnieje jednak inna klasa synchronizacji dostępna od Delphi 2009: TMonitor. wykorzystuje blokadę przedmiotu, który został wprowadzony w tej wersji ...

+0

dlaczego TMutex byłby preferowany do sekcji krytycznej? Wydaje mi się dziwne. –

0

public właściwości klasy TWorker MUSI mieć get i set metod, dzięki czemu można używać Tcriticalsection dać wartości właściwości. W przeciwnym razie będziesz mieć problemy z wątkami. Twój przykład wydaje się być w porządku, ale w realnym świecie tysiące wątków uzyskujących dostęp do tej samej wartości spowodowałoby błąd odczytu. Używaj krytycznych sekcji .. i nie musisz używać żadnej synchronizacji. W ten sposób unikasz przechodzenia do kolejek komunikatów okien i zwiększania wydajności. Poza tym, jeśli użyjesz tego kodu w aplikacji usługi Windows (gdzie wiadomości systemu Windows nie są dozwolone), ten przykład nie zadziała. Metoda synchronizacji nie działa, jeśli nie ma dostępu do kolejki komunikatów systemu Windows.

+1

Usługi systemu Windows MOGĄ mieć komunikaty systemu Windows. Cała aplikacja Windows składa się z pompy komunikatów w ich rdzeniu –

0

Rozwiązany! (odpowiedź zaczerpnięta z pytania)
Poprawki wykonane dla tego problemu, gdy dwa razy.
Najpierw usuń wywołanie synchronizacji w metodzie TWorker SendBossResult.

Po drugie dodaj fProcessWorkerResult CritialSection do klasy TBoss. Utwórz i uwolnij to w tworzeniu/niszczeniu TBoss. W procesie ProcessWorkerResults wywołaj metodę fProcessWorkerResult.Enter i fProcessWorkerResult. Opuść kod, który musi być zabezpieczony przed strumieniowaniem wielu wyników pracowniczych.

Powyższe było podsumowaniem po kodzie Kens i komentarzem do niego. Wielkie dzięki, proszę pana, czapki do głowy!

Powiązane problemy