Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   frage zu threads und timer (https://www.delphipraxis.net/177972-frage-zu-threads-und-timer.html)

Wishmaster 9. Dez 2013 00:11

frage zu threads und timer
 
hi

ich arbeite mal wieder an meinen Bass(.dll) komponenten, die komponenten sind mittlerweile so kompliziert geworden
dass es immer schwieriger wird die zu managen! zum beispiel die Sound-Recorder komponente, die hat vier haupt threads(feeder)
und wenn einer der threads zu schnell oder zu langsam ist bricht das ganze zusammen.

und hier komme ich auch gleich zu meiner frage. was ist die eleganteste lösung um threads oder das innenleben:wink: perfect zu takten?

zur zeit benutze ich zwei verschieden methoden Sleep(x) und TSimpleEvent. vielleicht hat einer von euch noch einen dritten vorschlag
für den unteren code, das timing muss 100% präzise sein damit der buffer nicht lehr läuft.


Delphi-Quellcode:
procedure TBassRecorder.RecThreadWorkerCallback(Thread: TMultiThreadThread;
 Parameters : TObject; var Data, Results : TObject);
var
  FBuff : array [0..50000] of Byte;
  FData : DWORD;
  WorkMessage: TWorkMessage;
  WorkResult: TWorkResult;
begin
  WorkMessage := TWorkMessage.Create;
  WorkMessage.ID := TWork(Data).ID;
  WorkMessage.Text := 'Starting';
  WorkMessage.Status:= thStarting;
  Thread.SendMessage(WorkMessage, nil);

 repeat
   Sleep(TWork(Data).TimeToWait);            //TimeToWait = 1
  if not ThreadPause[THREAD_REC] then
   begin
     FData:= BASS_ChannelGetData(Channel[CHANN_REC_MIXER], @FBuff, SizeOf(FBuff));
    if FData = DW_ERROR then
     begin
      Break;
      // Error
     end;
   end;
 until
  Thread.Terminated or (Channel[CHANN_REC_MIXER] = 0);


 if Thread.Terminated then
  begin
   WorkMessage := TWorkMessage.Create;
   WorkMessage.ID := TWork(Data).ID;
   WorkMessage.Text := 'Terminated';
   WorkMessage.Status:= thTerminated;
   Thread.SendMessage(WorkMessage, nil);
  end;

  WorkResult := TWorkResult.Create;
  WorkResult.ID := TWork(Data).ID;
  WorkResult.Text := 'Finished';  //finished successfuly.
  WorkResult.Status:= thFinished;
  Results := WorkResult;
end;


Delphi-Quellcode:
procedure TBassRecorder.VasThreadWorkerCallback(Thread: TMultiThreadThread; Parameters: TObject; var Data, Results: TObject);
var
  Time : QWORD;
  Level : DWORD;
  Active : Boolean;
  Timer: TWaitResult;

  WorkMessage : TWorkMessage;
  WorkResult : TWorkResult;
begin
  WorkMessage := TWorkMessage.Create;
  WorkMessage.ID := TWork(Data).ID;
  WorkMessage.Text := 'Starting';
  WorkMessage.Status:= thStarting;
  Thread.SendMessage(WorkMessage, nil);

 repeat
  if not ThreadPause[THREAD_VAS] then
   begin

     Level:= BASS_Mixer_ChannelGetLevel(Channel[CHANN_VIS_SPLIT]);
     Active:= (LOWORD(Level) >= FVAS_Threshold) or
              (HIWORD(Level) >= FVAS_Threshold);

    (* sound is coming through *)
    if Active then
     begin

      (* continue recording when the sound starts up again. *)
       ...
      (* start a new recording ehen the sound starts up again. *)
       ...
     end;
                                 

     Timer:= TWork(Data).TimeEvent.WaitFor(TWork(Data).TimeToWait);   // 1000 = 1 Sec.
    if Timer = wrTimeout then
     begin
      (* sound is not coming through *)
      if not Active then
       begin

        (* Pause Recording after X Sec. of silence *)
         ...

        (* Pause Recording after X Sec. of silence *)
         ...

        (* Stop Recording after X Sec. of silence *)
         ...

       end;
     end;

   end;
 until
  (FVAS_ForceStop) or
  (Timer <> wrTimeout) or
  (Thread.Terminated) or
  (Application.Terminated);

 if Thread.Terminated then
  begin
   WorkMessage := TWorkMessage.Create;
   WorkMessage.ID := TWork(Data).ID;
   WorkMessage.Text := 'Terminated';
   WorkMessage.Status:= thTerminated;
   if Assigned(TWork(Data).TimeEvent) then
    TWork(Data).TimeEvent.SetEvent;
   Thread.SendMessage(WorkMessage, nil);
  end;

  WorkResult := TWorkResult.Create;
  WorkResult.ID := TWork(Data).ID;
  WorkResult.Text := 'Finished';  //finished successfuly.
  WorkResult.Status:= thFinished;
  if Assigned(TWork(Data).TimeEvent) then
   TWork(Data).TimeEvent.SetEvent;
  Results := WorkResult;
end;

Zacherl 9. Dez 2013 00:41

AW: frage zu threads und timer
 
Ohne jetzt genau deinen Code zu betrachten: Definitiv Events! Sleep ist extrem unzuverlässig. Du kannst als WaitTime auch die Konstante INFINITE verwenden. Dann wartet der Thread solange, bis vom anderen Thread aus das Event signalisiert wird.

Ich denke du solltest dich generell in den Themenkomplex "Threadsynchronisierung" mal etwas einlesen. CriticalSections könnten auch eine Möglichkeit sein konkurrierende Zugriffe auf den Buffer zu managen.

Furtbichler 9. Dez 2013 08:05

AW: frage zu threads und timer
 
Stell Dir einfach vor, deine 4 Threads sind 4 Personen, die gleichzeitig irgend etwas machen, vermutlich in einer Schleife.

Also: 1 Koch, der auf Bestellungen wartet, diese zubereitet und dann in der Durchreiche abstellt und vermutlich irgend eine Bimmel betätigt.

1 Gast, der Platz nimmt, Hunger bekommt oder hat, auf einen Ober wartet, eine Bestellung aufgibt, auf das Essen wartet, isst, rülpst, bezahlt und geht.

1 Ober, der Bestellungen vom Gast aufnimmt, diese zur Küche bringt und (wie?) an die Küche übermittelt.

1 Ober, der die fertig gekochten Gerichte aus der Küche zum Gast bringt, abräumt und ggf. abkassiert.

1 Inhaber, der blöd in der Ecke rumhängt, sich einen Cocktail nach dem anderen hinter die Binde knallt und mit der Barfrau schäkert.

Gut, der letzte muss jetzt nicht synchronisiert werden, aber da kann man lernen, das es Threads gibt, die Überflüssig wie ein Kropf sind.

Was ich meine sind die Schnittstellen zwischen den Threads: Würde der Koch nach dem Kochen eines Gerichts warten, bis dieses abgeholt wird, würden sich die Bestellungen vermutlich stapeln, oder man bräuchte mehrere Köche. Er könnte auch alle paar Sekunden zur Bestellannahme gehen und schauen, ob es was neues zu tun gibt. Besser, aber auch blöd, weil er dann auch mal umsonst schauen geht. Was ist also der beste Weg?

Wenn der Ober so lange vor dem Gast auf und ab wippt, bis dieser sich entschieden hat, ist das auch keine gute Sache, denn der Gast wird dem Ober vermutlich irgendwann eins mit der Karte übersemmeln oder gleich zum Inhaber gehen, der dann doch eine gewisse Funktion hat...

Die Lösung ist die 'asynchrone Kommunikation' (Bimmel, 'Her Ooober', 'Zahlen bitte') sowie Queues (Essensausgabe, Bon-Leiste usw).

Lustig ist, das in einem Restaurant ein Restaurantleiter etwaige Staus auflöst und für einen reibungslosen Ablauf sorgt. Ein ähnlicher Controller würde in kritischen Systemen die Threads und Übergaben auch überwachen (overflows etc.) und ggf. eingreifen.

Ein Thread reagiert auf Events, und löst selber Events aus, wenn er einem anderen Thread mitteilen will, das dieser weiterarbeiten kann. Der Thread nimmt Aufträge aus einer Queue, führt diese aus und schreibt sein Ergebnis wieder in eine Queue usw.

Der Controller prüft die Queues und reagiert, wenn diese so groß werden: Dann ist ein Thread entweder abgeschmiert (gaaanz schlecht) oder kommt mit der Arbeit einfach nicht hinterher (zweiten Thread starten: Fertig). Oder das System ist dann einfach überlastet: Eine Küche mit zwei Herden (=limitierte Resourcen) ist auch mit 20 Köchen nicht in der Lage, mehr als 30 Personen zu bewirten...

Wishmaster 9. Dez 2013 08:31

AW: frage zu threads und timer
 
danke für die schnelle antwort.

über die Sleep funktion hatte ich mir auch schon gedanken gemacht das die vielleicht nicht die beste lösung ist.
und dass mit der INFINITE Konstante macht in meinem fall keinen sinn.
ich habe für jeden thread drei verschiedene Callback funktionen die mir genau sagen wann und was abgeht;
und somit kann ich auf die anderen threads reagieren.

mir ging es hauptsächlich ums perfect timing from within the loop.


Delphi-Quellcode:
procedure TBassRecorder.RecThreadWorkerCallback(Thread: TMultiThreadThread;
 Parameters : TObject; var Data, Results : TObject);
begin
  // main thread wo sehr hart gearbeitet wird
end;


procedure TBassRecorder.VasThreadResultsCallback(Sender: TObject; var Data, Results: TObject);
var
  WorkResult: TWorkResult;
begin
  WorkResult := TWorkResult(Results);
 if WorkResult.Status = thFinished then
  begin
   ThreadFinished[THREAD_VAS]:= True;
  end;
end;


procedure TBassRecorder.VasThreadMessage(Sender: TObject; var Message1, Message2: TObject);
begin
  // test
 if TWorkMessage(Message1).Status = thStarting then
  begin
   ThreadFinished[THREAD_VAS]:= False;
  end;

 if TWorkMessage(Message1).Status = thTerminated then
  begin
   ThreadFinished[THREAD_VAS]:= True;
  end;
end;

wie schon gesagt das ding ist ein kompliziertes monster geworden. aber ich denke ich habe die meisten fehler ausgemerzt.
natürlich muss ich das ganze auch auf anderen Systemen testen.


hat nichts mit dem thema zutun aber rein theoretisch, welche features würdet ihr euch in ein Sound-Recorder wünschen


thanks again for your help.


Alle Zeitangaben in WEZ +1. Es ist jetzt 05:50 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz