Guten Abend, und danke für Deine schnelle Antwort!
willi4willi hat geschrieben:Hallo Armin,
wenn du es so machst, wie beschrieben, dann würde ich vor den Füllen des Buffers eine boolsche Variable setzen, die anschließend wieder zurückgesetzt wird.
Wenn der Buffer ausgelesen werden soll, dann sollte diese Variable nicht gesetzt sein, dann dann wird ja gerade was reingeschrieben.
Gibt es eigentlich einen Grund, warum du bei einkommenden Daten keinen Event auslöst?
Ich machs tatsächlich mit einem Event. Der Teil ist der Verkürzung des Beispielcodes zum Opfer gefallen, weil er m.E. nichts mit dem gemeinsamen Zugriff zu tun hat, und weil es noch x andere Events (Fehlerbedingungen) gibt. Ich löse einen Event aus, so lange im ReceiveBuffer mindestens ein CR gefunden wird.
Code: Alles auswählen
// Empfangsversuch, 200ms lang alle einlaufenden zeichen bündeln.
// CR ist bereits in den Eingangsdaten enthalten
ReceiveBuffer := ReceiveBuffer + ComRead(200);
if pos(cr,ReceiveBuffer) > 0 then CommandReceived.SetEvent;
...
... und die Lese -Threads arbeiten prinzipiell so
Code: Alles auswählen
...
if TSerialThread.CommandReceived.WaitFor(5000) = wrSignaled then
Value := TSerialThread.Receive;
else
// irgendeine Fehlerbehandlung --> Timeout, das Peripheriegerät sendet nix mehr
... aber das ist m.E. eine andere Baustelle. Damit kann ein potenzieller Empfänger warten bis es sich lohnt, überhaupt receive aufzurufen. Ich denke, das habe ich richtig gemacht. Mir gehts nur um die beiden Buffer in der Empfänger-Routine, da auf diese gemeinsam zugegriffen wird.
Ich habe jetzt auch ehrlich eine halbe Stunde über eine boolean Variable nachgedacht, eigentlich sinds ja zwei, eine für jeden Buffer? Wie genau sollen die mich retten? Da execute, send und receive im gleichen Thread laufen, und ich ja irgendwie darauf warten müsste, dass die Variable freigegeben wird, würde ich den Thread in einen Deadlock schicken:
Code: Alles auswählen
... in execute:
Receiving := true;
// Empfangsversuch, 200ms lang alle einlaufenden zeichen bündeln.
// CR ist bereits in den Eingangsdaten enthalten
ReceiveBuffer := ReceiveBuffer + ComRead(200);
if pos(cr,ReceiveBuffer) > 0 then CommandReceived.SetEvent;
Receiving := false;
... in receive
function TSerialThread.Receive : String;
// hier holen andere Threads empfangene Daten ab,
// immer ein cr getrenntes Paket auf einmal, "" wenn der
// Puffer leer ist
begin
result := '';
while Receiving do ;
if pos(CR,ReceiveBuffer) > 0 then
begin
result := LeftOfCR(ReceiveBuffer);
ReceiveBuffer := RightOfCR(ReceiveBuffer);
end;
end;
Sobald Receive zufällig innerhalb des Empfangsfensters aufgerufen würde, würde die while Schleife den kompletten TSerialThread zum Stehen bringen. Ich müsste den Code von Receive irgendwie aus dem Thread auskoppeln, oder gruselige Ansätze wie Delays verteilen versuchen, damit execute irgendwann weiter läuft. Und würde mir sofort die nächste Ohrfeige einfangen: während receive seine Artbeit macht könnte execute die nächste Loop durchlaufen und receive einholen, also nochmal eine Variable, mit der execute am Empfängerteil vorbeigelotst wird bis receive wieder verlassen wurde? Klingt für mich vom Gefühl her wie eine Einladung zum Deadlock.
Ich denke, ich muss das auf Thread-Ebene lösen. Immer wenn execute so einen send/receive Zyklus durchlaufen hat, also am Anfang oder am Ende der while not terminated Schleife, müssen die anderen Threads ihre Arbeit machen. Ich dachte, das sei der Sinn von CriticalSection: ich würde eine um den Sender und eine um den Empfänger-Block in execute schreiben, so lange sich der Code von execute innerhalb eines dieser beiden Blöcke befindet haben die anderen Threads Pause. Und in Send und Receive ebenso, damit der execute Thread vor ihren Aktivitäten geschützt wird.
So irgendwie:
Code: Alles auswählen
procedure TSerialThread.execute;
begin
while not terminated do
begin
// Wenns was zu senden gibt, raus damit.
if pos(CR,SendBuffer) > 0 then
begin
[b] EnterCriticalSection(CritSect);[/b]
ComSend(LeftOfCR(SendBuffer));
SendBuffer := RightOfCR(SendBuffer);
[b] LeaveCriticalSection(CritSect);[/b]
end;
// Empfangsversuch, 200ms lang alle einlaufenden zeichen bündeln.
// CR ist bereits in den Eingangsdaten enthalten
S := ComRead(200);
if S <> '' then
begin
[b] EnterCriticalSection(CritSect);[/b]
ReceiveBuffer := ReceiveBuffer + S;
[b] LeaveCriticalSection(CritSect);[/b]
end;
end;
procedure TSerialThread.Send(S:String);
// Hier kippen andere Threads die zu sendenden Daten ab
begin
[b] EnterCriticalSection(CritSect);[/b]
SendBuffer := SendBuffer + CR + S;
[b] LeaveCriticalSection(CritSect);[/b]
end;
function TSerialThread.Receive : String;
// hier holen andere Threads empfangene Daten ab,
// immer ein cr getrenntes Paket auf einmal, "" wenn der
// Puffer leer ist
begin
result := '';
if pos(CR,ReceiveBuffer) > 0 then
begin
[b] EnterCriticalSection(CritSect);[/b]
result := LeftOfCR(ReceiveBuffer);
ReceiveBuffer := RightOfCR(ReceiveBuffer);
[b] LeaveCriticalSection(CritSect);[/b]
end;
end;
Das basiert jetzt aber mehr auf meinem Wunschdenken bzw. einer vagen Idee wie mit CriticalSection umzugehen ist. Wem "gehört" eigentlich der Codfe von TSerialThread.Send bzw. TSerialThread.Receive? Dem SerialThread, wo die Routinen drinnen stehen, oder dem Thread der die beiden Routinen gerade aufgerufen hat? Wäre es so, dann würde die Geschichte mit der Variablen wieder irgendwie plausibler.
Armin.