Thread blockiert

Für alles, was in den übrigen Lazarusthemen keinen Platz, aber mit Lazarus zutun hat.
Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Thread blockiert

Beitrag von Scotty »

Ich versuche eine Netzwerk-Umfrage in mein Programm einzubauen und da hängt es gerade. Ich suche seit einer gefühlten Ewigkeit und finde keinen Grund. Vielleicht hilft mir das Aufschreiben oder ihr habt einen Tipp.
Ich habe eine Klasse TPoll abgeleitet von TThread geschrieben, die eine eigene WaitforResult-Methode hat. Im Main-Loop wird initialisiert, eine Nachricht ins Netzwerk verschickt und auf diese Methode gewartet. Das klappt ausgezeichnet, bis auf den Fall, dass ich von einem Timer gesteuert die Routine aufrufe. Während der Timer tickt, läuft alles bestens. Nur dann, wenn der Timer die "OutOfTime"-Methode aufruft, kommt gar nichts mehr durch. Leider muss ich wohl einiges an Code posten :oops:

Code: Alles auswählen

procedure TPoll.SynchMethod;
begin
  Application.ProcessMessages;
end;
 
procedure TPoll.WaitforResult;
begin
  while not Done and not Application.Terminated do
  begin
    Sleep(100);
    Synchronize(@SynchMethod);
  end;
  if assigned(aForm) and aForm.Visible then aForm.Hide; //aForm ist zur Rückmeldung da. 
end;
 
procedure TPoll.SetAnswer(aSender: string; const aValue: boolean);
var i:integer;
begin
  for i:=0 to NumberOfPlayers-1 do
  if PlayerName(i)=aSender then
  begin
    case aValue of
     true  : FPollState[i]:=plYes;
     false : FPollState[i]:=plNo;
    end;
    if assigned(aForm) and (aForm.Visible) then
    case aValue of
     true  : aImage[i].Picture.Bitmap.LoadFromLazarusResource('plYes');
     false : aImage[i].Picture.Bitmap.LoadFromLazarusResource('plNo');
    end;
    break;
  end;
  aButton.Enabled:=true;
  FPollResult:=true;
  for i:=0 to NumberOfPlayers-1 do
  begin
    aButton.Enabled:=aButton.Enabled and (FPollState[i]<>plWait);
    FPollResult:=FPollResult and (FPollState[i]=plYes);
  end;
  if aButton.Enabled and (not aForm.Visible or aCheckBox.Checked) then
    DoButtonClick(self);
end;
 
procedure TPoll.DoButtonClick(Sender: TObject);
begin
  done:=true;
end;

Code: Alles auswählen

procedure TThreadTimer.Execute;
var z:Longword;
begin
  while not Terminated do
  begin
    if (FTimeLeft=0) then Suspend;
    Sleep(1000);
    if (FTimeLeft>0) and not Terminated then
    begin
      z:=(GetTickCount-FStart) div 1000;
      if FInterval>z then FTimeLeft:=FInterval-z else FTimeLeft:=0;
      Synchronize(@SyncMethod);
    end;
  end;
end;
 
procedure TThreadTimer.SyncMethod;
begin
  if assigned(FOnTimerTick) then FOnTimerTick(FTimeLeft);
  if (FTimeLeft=0) and assigned(FOnTimer) then FOnTimer(self);
end;

Code: Alles auswählen

procedure TfmMain.DoTimer(Sender: TObject);
begin
  acNextPlayerExecute(self);
end;
 
procedure TfmMain.acNextPlayerExecute(Sender: TObject);
...
      Poll.Initialize(false);//false -> aForm.Visible=false
      TCPClient.Send(nwCheckWord,TCPClient.Name,bRec[fmNetwork.IsGameServer],sl[i]); //Nachricht ins Netzwerk
      Poll.WaitforResult;
      if not Poll.Result then...
...
"Klicke" ich manuell auf acNextPlayer läuft alles einwandfrei durch. Macht es der Timer, dann schickt das Programm die Nachricht raus, bekommt aber keine Antworten zurück (ich schicke das auch an mich selbst). Es hängt in der Waitfor-Schleife und macht Application.Processmessages, ohne dass der Netzwerk-Thread noch bedient wird. Ich habe schon versucht, Suspend aus dem Timer zu nehmen - gleiches Ergebnis. Ich habe versucht, die Umfrage ohne Thread laufen zu lassen - erfolglos.
Hat jemand eine Idee?

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Thread blockiert

Beitrag von mse »

Was macht tpoll.execute()? tthreadtimer ist ein weiterer tthread? Warum verwendest du keinen ttimer?
Bei synchronize() wird die synchronizeproc im Kontext des main thread aufgerufen, da macht application.processmessages IMO keinen Sinn. Wenn ich richtig verstehe, läuft tpoll.waitforresult() im Kontext des mainthread darum macht synchronize() hier IMO auch keinen Sinn.

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: Thread blockiert

Beitrag von Scotty »

Im Execute steht while not Terminated do sleep(1). Ursprünglich dachte ich bei Thread an Waitfor, aber dann wird der Mainthread nicht weiter ausgeführt. Deshalb "sieht" das alles nach Thread aus, ist aber letzten Endes nicht so sinnvoll, stimmt. Wenn ich ProcessMessages weglasse, wird das Umfrage-Formular nicht gezeichnet. TTimer ist mir zu ungenau - ich lasse die Uhren lokal laufen.
Ansonsten: Ich bin offen für Alternativvorschläge 8)

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Thread blockiert

Beitrag von mse »

Scotty hat geschrieben:Im Execute steht while not Terminated do sleep(1). Ursprünglich dachte ich bei Thread an Waitfor, aber dann wird der Mainthread nicht weiter ausgeführt. Deshalb "sieht" das alles nach Thread aus, ist aber letzten Endes nicht so sinnvoll, stimmt. Wenn ich ProcessMessages weglasse, wird das Umfrage-Formular nicht gezeichnet. TTimer ist mir zu ungenau - ich lasse die Uhren lokal laufen.
Ansonsten: Ich bin offen für Alternativvorschläge 8)
gettickcount() ist nicht genauer als ttimer. Ich würde hier zuerst einmal gründlich aufräumen. Und suspend ist "evil". ;-)

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Thread blockiert

Beitrag von mse »

Scotty hat geschrieben:Ich versuche eine Netzwerk-Umfrage in mein Programm einzubauen und da hängt es gerade. Ich suche seit einer gefühlten Ewigkeit und finde keinen Grund. Vielleicht hilft mir das Aufschreiben oder ihr habt einen Tipp.
Ich habe eine Klasse TPoll abgeleitet von TThread geschrieben, die eine eigene WaitforResult-Methode hat. Im Main-Loop wird initialisiert, eine Nachricht ins Netzwerk verschickt und auf diese Methode gewartet.
Warum musst du auf waitforresult warten? Wäre es nicht besser, im Kommunikationsthread nach Abschluss der Übertragung Aktionen auszulösen? application.processmessages() sollte wenn immer möglich vermieden werden, bei Verwendung von threads sowieso, da application.processmessages() ja vielfach ein halbgarer Ersatz für threads ist.

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: Thread blockiert

Beitrag von Scotty »

Timer: Wenn ein modales Fenster bei mir aktiv ist, wird alles andere angehalten, auch ein Timer. (Linux, 64bit) Durch GetTickCount kann ich das ausgleichen. Ansonsten ist IMHO GTC genauer, aber in einem Bereich, der mich nicht interessiert :?

"Suspend ist evil": Warum? Resume/Suspend wird nur im Timerthread aktiviert.

Waitfor: Wenn eine Umfrage erfolgt, entscheidet sich der weitere Programmablauf durch das Ergebnis. Das Beispiel ist ein simpler Test, ob ein Wort bei allen Spielern bekannt ist (wenn nicht, dann wird offen gefragt, was getan werden soll). Wenn zum Beispiel eine Pause gemacht werden soll, dann frage ich alle Mitspieler, ob das in Ordnung ist, und pausiere erst, wenn alle zugestimmt haben. Prinzipiell wären Events möglich, aber ich setze den Poll an verschiedenen Stellen ein und suche eine generische Lösung. Ein Gedanke war, die Umfrage als Thread zu erstellen und dann per Thread.Waitfor auf Terminate zu warten. Dabei hatte ich Probleme mit dem initialisieren und der Ergebnisauswertung, wenn ich mich recht erinnere. Hab schon recht viel daran herum gebastelt.
Mir ist aber absolut nicht klar, warum es in der einen Bedingung funktioniert und in der anderen nicht. Ich kann leicht mein Thread-Gemurkse aus der Umfrage rausnehmen (TPoll=class) und lasse einfach WaitforResult rotieren, bis done gesetzt wird. Ohne Timer-Event funktioniert es, mit geht es nicht.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Thread blockiert

Beitrag von mse »

Scotty hat geschrieben:Timer: Wenn ein modales Fenster bei mir aktiv ist, wird alles andere angehalten, auch ein Timer. (Linux, 64bit)
Aha, das ist dann aber ein bug. Lazarus? Schon gemeldet?
"Suspend ist evil": Warum? Resume/Suspend wird nur im Timerthread aktiviert.
Resume kann kaum im Timerthread sein, da er sich ja selbst suspended hat? Aber du hast schon recht suspend ist sicher, falls ausschliesslich im eigenen thread aufgerufen und keine locks gehalten werden. Ich finde in so einem Fall ein Semaphore angemessener.
Waitfor: Wenn eine Umfrage erfolgt, entscheidet sich der weitere Programmablauf durch das Ergebnis.
Ist die dabei GUI gesperrt oder funktional? Wie wird der Wartezustand signalisiert? Je nach dem kommen andere Realisierungen in Frage. Falls die GUI aktiv und bedienbar sein soll, würde ich einen thread starten, der auf die Antworten wartet und die Ergebnisse in die mainthread-Ablauflogik einträgt. Zur Synchonisation thread/mainthread benutze ich jeweils application.lock()/unlock(). Ich weiss nicht ob Lazarus lock()/unlock() hat, ansonsten muss synchronize() verwendet werden. Eine andere Möglichkeit ist, messages durch die main-eventloop an die Ablauflogik zu posten.

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: Thread blockiert

Beitrag von Scotty »

mse hat geschrieben:
Scotty hat geschrieben:Timer: Wenn ein modales Fenster bei mir aktiv ist, wird alles andere angehalten, auch ein Timer. (Linux, 64bit)
Aha, das ist dann aber ein bug. Lazarus? Schon gemeldet?
http://bugs.freepascal.org/view.php?id=14318" onclick="window.open(this.href);return false;

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Thread blockiert

Beitrag von mse »

Scotty hat geschrieben: http://bugs.freepascal.org/view.php?id=14318" onclick="window.open(this.href);return false;
Meinst du
0014318: Modal dialogs at dragging
? Damit timer events geliefert werden können, muss die main eventloop laufen. Ist möglicherweise nicht showmodal() das Problem sondern eine Wartezustand in einem main thread eventhandler?

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Thread blockiert

Beitrag von mschnell »

Scotty hat geschrieben:Im Execute steht while not Terminated do sleep(1).
Ganz schlechte Idee! Dieser Thread tut nichts außer CPU-Zeit verbraten.
In execute muss das stehen (bzw. aufgerufen werden), was der Thread tun soll. Wenn der Thread auf irgendwas warten soll, (Z.B. auf eine TCP/IP-Antwort), muss das durch einen "blocking" Aufruf (z.B. read an TCP/IP) geschehen. D.h. ein Thread kann nur auf ein "Ereignis" auf einmal warten (es gibt Tricks z.B. mit "WaitForMultipleObjects()" in Windows oder "select()" in Linux, wird aber durch Lazarus nicht besonders unterstützt, sondern ist rein Sache des Betriebssystems).

Timer kann man in Lazarus-threads nicht verwenden. TTimer-Ereignisse erzeugen Einträge in die Event-Queue, die ausschließlich vom Mainthread bearbeitet wird. Mehrere Event-Queues kann Lazarus (noch) nicht.

Wenn der Thread auf ein Ereignis pollen muss, sollte man ein längeres sleep verwenden, damit der Rechner (auch System-Aufgaben, wie die TCP/IP-Bearbeitung des Betriebssystems) nicht von dem Thread blockiert wird.
Scotty hat geschrieben:ich lasse die Uhren lokal laufen
Was soll das heißen ? Darstellung auf dem Bildschirm ist im Thread verboten. Dazu musst Du ein Mainthread-Ereignis verwenden und mit "Synchroize" (was den Thread blockiert) oder einem TTimer aufrufen.
Wenn Du außerhalb des Mainthreads gleichzeitig auf eine Rückmeldung von TCP/IP warten und die Systemzeit feststellen willst, brauchst Du zwei Threads. Ob das aber "schneller" oder genauer ist als ein TTimer im Mainthread für die Zeitmessung, ist ziemlich fraglich. Ernsthaftes "realtime"-Verhalten ist nur mit Lazarus nicht realisierbar, dazu brauchst Du die explizite Verwendung von Betiebssystem-Mittel.

-Michael

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: Thread blockiert

Beitrag von Scotty »

Danke für die vielen Antworten. Ich glaube aber, dass es gerade etwas vom Thema abweicht - deswegen nochmal meine Frage.
Ich will die Bearbeitung des Hauptthreads konditional aufgrund eines Netzwerkereignisses behandeln. In etwa so: if Bedingung_A and TCP_Frage_Mitspieler then... Der TCPClient läuft natürlich in einem eigenen Thread. Die Abfrage habe ich zum Testen verthreaded, das aber wieder rausgenommen. Dort gibt es jetzt nur eine WaitForResult-Schleife in der Sleep und Application.ProcessMessages gemacht wird. Die Abbruchbedingung wird über eine Property gesetzt: property Answer... SetAnswer, for i:=0 to Spieleranzahl-1 do done:=done and Answer. Diese Prozedur funktioniert einwandfrei im normalen Programmablauf. Starte ich aber statt Klick auf einen Toolbutton die Action programmgesteuert nach einem Timer-Event, dann hängt die Umfrage im WaitForResult und ich bekomme auch nichts mehr vom TCPClient.
Natürlich wäre ein Event schön, aber ich kann mir nicht vorstellen, wie ich das in dieser Abfrage einbauen könnte (prinzipiell weiß ich, wie es geht und benutze Events auch). Ich wundere mich eigentlich, warum es bei einem kaum abweichenden Aufruf nicht klappt.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Thread blockiert

Beitrag von mse »

Hast du denn mittlerweile den TCPClient thread von application.processmessages() und so weiter gereinigt? Poste doch bitte nochmals den aktuellen code.
Warten in einer mainthread-Schlaufe mit processmessages ist schlecht, da kommt es immer wieder zu Überraschungen. Zeige ein modales Fenster "Wir warten auf Antwort..." stattdessen.
Sobald die Antwort eingetroffen ist, setzt der TCP thread entweder direkt modalresult des Fensters oder setzt ein Flag-Property in der Spiele-Status- Maschine, welches die Entsprechenden Aktionen vornimmt (besser).
Falls ttimer in einem modalen Fenster blockiert ist, ist es ein bug der behoben werden muss.
Das modale Fenster ist nicht unbedingt notwendig, die Status-Maschine kann auch ohne funktionieren. Mit anderen Worten, ich glaube nicht, dass du in einer Procedure des mainthread auf die Antwort warten musst und solltest.

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: Thread blockiert

Beitrag von Scotty »

Ein modales Fenster hatte ich bisher drin. Leider wollen die Benutzer während der Umfragen auch chatten können und dann geht das nicht mehr modal. Ich glaube aber mittlerweile, dass ich nur ein Sequenzproblem habe und irgendwo die Initialisierung nicht richtig erfolgt. Mein (nicht ganz aktueller) Code ist auf Lazforge. Im TCP-Thread und im Timerthread gibt es natürlich kein ProcessMessages.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Thread blockiert

Beitrag von mse »

Scotty hat geschrieben:Ein modales Fenster hatte ich bisher drin. Leider wollen die Benutzer während der Umfragen auch chatten können und dann geht das nicht mehr modal.
Dann solltest du auf jeden Fall auf die Wartefunktion mit ProcessMessages() im mainthread verzichten, sonst kommst du in Teufels Küche, falls die komplette Applikation mit ProcessMessages() laufen soll.

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: Thread blockiert

Beitrag von Scotty »

Dann solltest du auf jeden Fall auf die Wartefunktion mit ProcessMessages() im mainthread verzichten
Das verstehe ich nicht. Gib mal bitte ein Beispiel, was passieren kann.
Ich habe eben die DoTimer-Funktion manuell aufgerufen - dann stimmt wieder alles. Es muss :?: also daran liegen, dass in der SynchMethod() per OnTimer das DoTimer ausgelöst wird. Ich würde an der Stelle aber keinen Unterschied erwarten, da Synchronize doch im Hauptthread arbeitet.

Antworten