Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi var-Parameter mit Thread nutzen (https://www.delphipraxis.net/108367-var-parameter-mit-thread-nutzen.html)

Viktorii 12. Feb 2008 14:05


var-Parameter mit Thread nutzen
 
Hallo.
In einem Thread wird eine Funktion (USBAsyncRead) aufgerufen, welche als Argument einen var-Parameter benötigt. In diesen var-Parameter werden die Daten geschrieben, welche von dieser Funktion generiert werden.
Nun suche ich einen Weg diese Daten nach Beendigung des Threads, außerhalb des Threads weiterverwenden zu können. Daher habe ich versucht meinem Thread einen var-Parameter (tData) zu übergeben, welchen ich dann an die oben erwähnte Funktion weitergebe.

Das sieht so aus:
Delphi-Quellcode:
type
  TBulkCommandThread = class(TThread)
  private
    ...
    ...
    Data                   : TDataFrame;
    ...
    ...
  protected

  public

  end;


  TBulkReceiveCommandThread = class(TBulkCommandThread)
  private
    ...
    ....
  protected
    procedure Execute; override;
  public
    constructor create(tPipeHandle   : USB_PIPE_HANDLE;
                       var tData     : TDataFrame;
                       tDSPMessage   : Pointer;
                       tStatus       : Pointer);
  end;



constructor TBulkReceiveCommandThread.create(tPipeHandle   : USB_PIPE_HANDLE;
                                             var tData     : TDataFrame;
                                             tDSPMessage   : Pointer;
                                             tStatus       : Pointer);
begin
    PipeHandle     := tPipeHandle;
    Data           := tData;
    DSPMessage     := tDSPMessage;
    Status         := tStatus;
    inherited create(False);
end;





procedure TBulkReceiveCommandThread.Execute();
begin
  ...
  ...

  // receive Data
  bRet := USBReadAsync(PipeHandle,      
                       Data,
                       USBcb.nDATA,
                       FenumProcInst);
  ...
  ...
end;
Die Daten liegen nun in Data, sind aber außerhalb des Threads nicht verfügbar. Erscheint mir auch logisch, weil Data wohl nicht mehr das Selbe ist wie der wirkliche var-Parameter tData.
Wie kann ich mein Problem lösen?

Habe auch schon einen Lösungsansatz mit Zeigern probiert, aber ein Problem ist schon mal, dass USBAsyncRead einen var-Parameter und keinen Zeiger benötig. Ich weiß zwar, dass ein var-Parameter intern als Zeiger behandelt wird, aber wenn ich einen Zeiger übergebe, ändert sich auf einmal der Inhalt des Zeigers und nicht die Speicherstelle auf der er zeigt. Scheint mir also auch nicht der richtige Ansatz zu sein.

Habe auch mit Hilfe der SuFu keine Lösung finden können.

Wer weiß Rat?
Vielen Dank.

sirius 12. Feb 2008 14:10

Re: var-Parameter mit Thread nutzen
 
Da musst du wohl besser direkt einen Zeiger übergeben, denn derzeit wird in deinem Constructor TDataFrame kopiert.

Delphi-Quellcode:
type PDataFrame=^TDataframe;


constructor xyzThread.create(...; Data:PDataFrame);
//und jetzt immer mit PDataFrame arbeiten


//aufrufen kannst du den constructor dann so
xyzThread.create(... @Data);
//..wenn Data eine Variable vom Typ TDataFrame im HauptThread ist.
Achte dabei auch auf die Synchronisierung!

Viktorii 13. Feb 2008 07:39

Re: var-Parameter mit Thread nutzen
 
Hallo sirius. Vielen Dank für Deine Antwort.

Zitat:

Zitat von sirius
Da musst du wohl besser direkt einen Zeiger übergeben, denn derzeit wird in deinem Constructor TDataFrame kopiert.

Delphi-Quellcode:
type PDataFrame=^TDataframe;


constructor xyzThread.create(...; Data:PDataFrame);
//und jetzt immer mit PDataFrame arbeiten


//aufrufen kannst du den constructor dann so
xyzThread.create(... @Data);
//..wenn Data eine Variable vom Typ TDataFrame im HauptThread ist.

Ich verstehe das leider noch nicht so ganz. Okay, wenn ich es so mache, habe ich den Zeiger im Constructor. Wie komme ich in meiner Execute Prozedur an den Zeiger?

Das ist ja das Problem was ich bis jetzt auch hatte: Der var-Parameter stand mir im Constructor zur Verfügung. Ich wußte nicht wie ich in der Execute Prozedur an ihn rankomme. Also hab ich ihn in ein Feld kopiert. Allerdings macht dies natürlich die Funktionsweise des var-Parameters zu nichte...

Wie ich im ersten Post schon erwähnt hatte, braucht meine Funktion im Thread eigentlich einen var-Parameter und keinen Zeiger. Wenn ich ihr einen Zeiger übergebe, ändert sich der Inhalt des Zeigers, also die in ihm enthaltene Adresse und nicht die Speicherstelle auf der er zeigt.
Gibt es eine Möglichkeit einer Funktion, welche einen var-Parameter benötigt einen Zeiger zu übergeben?


Zitat:

Zitat von sirius
Achte dabei auch auf die Synchronisierung!

Werde ich drauf achten.

Vielen Dank

sirius 13. Feb 2008 08:02

Re: var-Parameter mit Thread nutzen
 
(Ich nehme an das TDataframe ein Record ist)

Ich versuchs mal an deinem Beispiel (geänderte Zeilen sind kommentiert)
Delphi-Quellcode:
type PDataframe=^TDataFrame; //hinzugefügt

type
  TBulkCommandThread = class(TThread)
  private
    ...
    ...
    Data                   : PDataFrame; //geändert
    ...
    ...
  protected

  public

  end;


  TBulkReceiveCommandThread = class(TBulkCommandThread)
  private
    ...
    ....
  protected
    procedure Execute; override;
  public
    constructor create(tPipeHandle   : USB_PIPE_HANDLE;
                       tData         : PDataFrame; //geändert
                       tDSPMessage   : Pointer;
                       tStatus       : Pointer);
  end;



constructor TBulkReceiveCommandThread.create(tPipeHandle   : USB_PIPE_HANDLE;
                                             tData         : PDataFrame;  //geändert
                                             tDSPMessage   : Pointer;
                                             tStatus       : Pointer);
begin
    PipeHandle     := tPipeHandle;
    Data           := tData;
    DSPMessage     := tDSPMessage;
    Status         := tStatus;
    inherited create(False);
end;





procedure TBulkReceiveCommandThread.Execute();
//hier würde ich zum synchronisieren den Inhalt von TDataframe kurz zwischensepichern
//var tempData=TDataFrame;
begin
  ...
  ...

  // receive Data
  tempData:=Data^; //ich weiß nicht, ob du Data vorbelegen musst (evtl. synchronisieren)
  bRet := USBReadAsync(PipeHandle,      
                       tempData,
                       USBcb.nDATA,
                       FenumProcInst);
 
  Data^:=tempData; //Diese Zeile muss synchronisiert werden
  ...
  ...
end;
Aufrufen kannst du den Constructor so:
Delphi-Quellcode:
//es existiert in der Klasse, die den Thread aufruft eine Variable DataFrame vom Typ TDataFrame;
BulkReceiveCommandThread:=TBulkReceiveCommandThread.create(tPipeHandle,
                                                           @DataFrame,
                                                           tDSPMessage,
                                                           tStatus);
Du übergibst jetzt mit PDataframe nicht den Record sondern einen Zeiger auf den Record. Der Record selbst existiert jetzt nur einmal im MainThread.

Viktorii 13. Feb 2008 09:46

Re: var-Parameter mit Thread nutzen
 
Jetzt ist mir klar geworden wie das funktioniert und bin schon mal ein ganzes Stück schlauer. Vielen Dank.

Über die Notwendigkeit des Synchornisierens und auch über den Grund dafür bin ich mir bewusst. Allerdings habe ich so etwas bis jetzt noch nie gemacht und weiß nicht wirklich wie man das macht. Macht man es in diesem Fall auch mit der TThread.Synchronize Methode?


Zitat:

Zitat von sirius
Delphi-Quellcode:
procedure TBulkReceiveCommandThread.Execute();
//hier würde ich zum synchronisieren den Inhalt von TDataframe kurz zwischensepichern
//var tempData=TDataFrame;
begin
  ...
  ...

  // receive Data
  tempData:=Data^; //ich weiß nicht, ob du Data vorbelegen musst (evtl. synchronisieren)
  bRet := USBReadAsync(PipeHandle,      
                       tempData,
                       USBcb.nDATA,
                       FenumProcInst);
 
  Data^:=tempData; //Diese Zeile muss synchronisiert werden
  ...
  ...
end;


Wie genau kann ich dies Zeile synchronisieren? Mir ist schon klar, dass tempData erst dann nach Data^ zugewiesen werden darf, wenn USBReadAsync das Schreiben in tempData beendet hat. Aber wie ist da der richtige Ansatz?

sirius 13. Feb 2008 10:02

Re: var-Parameter mit Thread nutzen
 
Was macht die Funktion "USBReadAsync"? Ist das ein weiterer Thread. Oder ist die Funktion blockierend?

Viktorii 13. Feb 2008 10:28

Re: var-Parameter mit Thread nutzen
 
Zitat:

Zitat von sirius
Was macht die Funktion "USBReadAsync"? Ist das ein weiterer Thread. Oder ist die Funktion blockierend?

Die Funktion blockiert nicht sondern kehrt sofort zurück und läuft so 'parallel' um so asynchrone Kommunikation zu ermöglichen. Muss dann eigentlich ein weiteren Thread aufmachen, denke ich... Ist eine Funktion die ich aus ner dll bekomme, kann also nicht nachschauen, was sie wirklich macht....

sirius 13. Feb 2008 10:51

Re: var-Parameter mit Thread nutzen
 
:gruebel: Dann machst du also einen Thread für einen Thread?
Ich sehe grad nicht durch um dir sinnvoll weiterzuhelfen.

Viktorii 13. Feb 2008 11:17

Re: var-Parameter mit Thread nutzen
 
Zitat:

Zitat von sirius
:gruebel: Dann machst du also einen Thread für einen Thread?
Ich sehe grad nicht durch um dir sinnvoll weiterzuhelfen.

Ja, weil die Funktion USBReadAsync zwei mal im Thread aufgerufen wird. Das zweite mal darf sie aber erst aufgerufen werden, wenn die erste beendet ist. Das kann ich mit WaitForSingleObject und Events, welche ich in den Callbackfunktionen der USBReadAsync Fkt. setze, kontrollieren.
Wenn nun aber die erste USBReadAsync Fkt. etwas länger braucht, wäre meine Anwendung für diese Zeit blockiert. Um dies zu umgehen, habe ich mir gedacht, ich lasse die beiden Funktionen in einem separaten Thread laufen.


Oder wäre es schon vernünftig synchronisiert, wenn ich die Zuweisung erst dann Vornehme, sobald das Event in der jeweiligen Callback Funktion (also USBReadAsync ist somit beendet) gesetzt worden ist?
Jetzt wo ich darüber nachdenke... ist doch bestimmt das damit gemeint, oder?

sirius 13. Feb 2008 12:38

Re: var-Parameter mit Thread nutzen
 
Ja, ich denke wir meinen dasselbe.

Du hast aber 2 Synchronisationsmomente. Einmal die Übergabe von DataFrame von dem USB-Thread auf deinen Thread (das machst du in der CallBackMethode oder nach dem Waitforxxxobject). Und dann nochmal von deinem Thread an den MainThread. Und da das in dem Fall dieselbe Zuweisung ist müsstest du 3 Thread gleichzeitig synchronisieren. Am einfachsten wäre es wahrscheinlich, das ganze in synchronize (-Methode) auszulagern.
Man könnte auch selber mit Messages arbeiten (oder Critical Sections). Was hier günstiger ist, kann ich so nicht sagen.

Viktorii 13. Feb 2008 15:24

Re: var-Parameter mit Thread nutzen
 
Okay, mit dem synchronisieren muss ich mich dann wohl nochmal eingehender befassen.
Allerdings hat sich bei meinem praktischem Versuch gerade leider noch ein Problem aufgetan. Das Zwischenspeichen (in tempData) muss ich ja nicht nur wegen der Synchronistation machen, sondern auch damit ich USBReadAsync eine Variable übergeben kann, welche diese dann als var-Parameter nutzen kann.
TDataFrame ist ein Array mit 2.000.000 Elementen vom Typ Byte. Das scheint für den Stack zu groß zu sein:

---------------------------
Benachrichtigung über Debugger-Exception
---------------------------
Im Projekt ... ist eine Exception der Klasse EStackOverflow mit der Meldung 'Stack-Überlauf' aufgetreten.
---------------------------
Anhalten Fortsetzen Hilfe
---------------------------

Das Array muss allerdings so groß (unter Umständen noch größer) sein. Komme ich um das kopieren der Daten irgendwie herum?
Wenn nein, muss ich schauen, wie ich den Stack-Überlauf umgehen kann....
Vielen Dank

sirius 13. Feb 2008 15:33

Re: var-Parameter mit Thread nutzen
 
Ich glaube du musst hier mal das ganze Konzept auf den Kopf stellen.

Was erwatest du denn in data? Und wie hats du TDataFrame derzeit deklariet?

Viktorii 14. Feb 2008 07:18

Re: var-Parameter mit Thread nutzen
 
Das Konzept ist scheinbar noch nicht optimal. Ich arbeite zum ersten mal mit Threads und bin versuche gerade herauszufinden welches das beste Konzept ist.

In Data erwarte ich eine bestimmte Anzahl von Byte. Die Anzahl kann variieren, von ein paar Byte, bis zu mehreren hundert kByte.

Die Deklaration von TdataFrame sieht so aus:

Delphi-Quellcode:
TDataFrame = array[0..2000000] of Byte;
Das Konzept für einmal Kommunizieren sieht so aus: Es wird immer ein Kommando gesendet und anschließend eins empfangen.
Ein Kommando besteht immer aus zwei Teilen: Header und Daten. D.h.: Es muss ein Header gesendet werden, wenn dieser Empfangen wurde, müssen die Daten gesendet werden. Wenn diese gesendet wurden, muss ein Header empfangen werden. Wenn der Header empfangen wurde, müssen die Daten empfangen werde.
Für einmal Kommunizieren erstelle ich einen Thread. In diesem Thread erstelle ich einen Thread für 'Sende Kommando'. In diesem Sende ich den Header, warte und sende die Daten. Damit wird dieser Thread beendet. Der Übergeordnete Thread hat solange gewartet und startet jetzt einen weiteren Thread zum Empfangen. Dieser empfängt den Header, wartet und empfängt die Daten. Dann beendet auch dieser Thread. Damit endet auch der übergeordnete Thread.
Ist dieses Vorgehen bei dieser Problemstellung so richtig?

sirius 14. Feb 2008 12:47

Re: var-Parameter mit Thread nutzen
 
Nicht nur an den Threads ist etwas nicht optimal, auch das mit dem TDataFrame ist irgendwie komisch. Jetzt fehlt mir aber Wissen um diese USB-Schnittstelle um dir weiterzuhelfen.

Muetze1 14. Feb 2008 12:51

Re: var-Parameter mit Thread nutzen
 
Wenn du TDataFrame als lokale Variable deklarierst, dann belegst du 2 MB auf dem Stack. Rufe diese Funktion dann noch einmal auf und dein Stack ist voll. Wenn du soviel Speicher brauchst, dann alloziier ihn dynamisch (GetMem(), etc).

Viktorii 14. Feb 2008 13:23

Re: var-Parameter mit Thread nutzen
 
Zitat:

Zitat von Muetze1
Wenn du TDataFrame als lokale Variable deklarierst, dann belegst du 2 MB auf dem Stack. Rufe diese Funktion dann noch einmal auf und dein Stack ist voll. Wenn du soviel Speicher brauchst, dann alloziier ihn dynamisch (GetMem(), etc).

Ja, das war mir klar, dass das der Grund für den Stacküberlauf ist. Danke für den Hinweis mit GetMem(). Werde mir die Routine anschauen....


Zitat:

Zitat von sirius
Nicht nur an den Threads ist etwas nicht optimal, auch das mit dem TDataFrame ist irgendwie komisch. Jetzt fehlt mir aber Wissen um diese USB-Schnittstelle um dir weiterzuhelfen.

Okay, aber das es hierbei um eine USB Kommunikation geht ist ehr nebensächlich. Es muss halt zwei mal eine (Sende-)Fkt. aufgerufen werden (diese braucht komischerweiser ein Pointer anstatt eines var-parameters, also muss ich hier nichts kopieren) und dann muss zwei mal die (Empfangs-)Fkt. aufgerufen werden (diese benötigt einen var-Parameter, anstatt eines Pointers, deswegen sehe ich hier das Kopieren der Daten bis jetzt als einzige, aber unschöne Lösung).
Die jeweils nächste Fkt. darf aber erst ausgeführt, wenn die Vorherige abgearbeitet wurde. Ob eine Fkt. abgearbeitet ist, kann ich in der jeweiligen Callback Fkt. feststellen, welche aufgerufen wird sobald eine Fkt. beendet ist.
Da die Menge der Daten die versendet/empfangen werden unterschiedlich groß sind, muss TDataFrame ein gewisse Größe haben.
Wie das Konzept meiner Threads ist, habe ich ja schon geschrieben. Für jegliche Anregungen oder Verbesserungsvorschläge bin ich sehr dankbar. Wie gesagt, ist das erste Mal das ich mit Threads arbeite.
Vielen Dank

sirius 14. Feb 2008 13:35

Re: var-Parameter mit Thread nutzen
 
Und bekommst du irgendwie heraus, wie viele Daten kommen.
(statt mit getmem zu arbeiten reicht auch schon ein dynamisches Array; Edit: oder ein String... ich würde je nach Datenart sogar ein Memorystream vorschlagen)

Viktorii 14. Feb 2008 13:58

Re: var-Parameter mit Thread nutzen
 
Zitat:

Zitat von sirius
Und bekommst du irgendwie heraus, wie viele Daten kommen.
(statt mit getmem zu arbeiten reicht auch schon ein dynamisches Array; Edit: oder ein String... ich würde je nach Datenart sogar ein Memorystream vorschlagen)

Ja ich weiss jedesmal (mehr oder weniger unmittelbar) bevor das Array an die Funktion übergeben wird, wie groß es sein muss(also wieviel Daten kommen bzw. rausgehen).
Von Memorystreams habe ich auch noch keine Ahnung. Werde ich mir auch mal anschauen...
Meinst Du dass die Threads, so wie sie mache, nicht gut gewählt sind? Es wird halt jedesmal ein Thread erstellt und anschließend wieder zerstört....

sirius 14. Feb 2008 14:04

Re: var-Parameter mit Thread nutzen
 
Das mit den Threads ist prinzipiell ok. Da das USBZeugs aber ergeignisorintiert läuft hätte man vielleicht auch auf den Thread verzichten können und eine "normale" Klassen benutzen können. Schaden tut es nicht.

Wenn du die Größe kennst, dann nimm doch ein dynamisches Array (var Data:array of byte) und setze jedesmal vor dem Aufruf die Länge (setlength(x,Länge)).
Musst du Data vorbelegen? Ich vermute nicht. Dann würde ich die Sache mit PDataFrame etc, ganz fallen lassen, sondern allein mittels synchronize arbeiten.

Wie startest du den Thread?
Was sind das für Daten?

Viktorii 14. Feb 2008 14:59

Re: var-Parameter mit Thread nutzen
 
Muss mich erst nochmal für Deine Geduld bedanken. Hab echt schon einige wertvolle Erkenntnisse sammeln können.

Zitat:

Zitat von sirius
Das mit den Threads ist prinzipiell ok. Da das USBZeugs aber ergeignisorintiert läuft hätte man vielleicht auch auf den Thread verzichten können und eine "normale" Klassen benutzen können. Schaden tut es nicht.

Welchen Thread genau hätte ich mir sparen können? Ich versuche die Struktur mal darzustellen:

Delphi-Quellcode:
USBJobThread
       |
       |_______SendCommandThread
       |                   |
       |                   |________USBWriteAsync
       |                   |
       |                   |________USBWritesync
       |
       |_______ReceiveCommandThread
                           |
                           |________USBReadAsync
                           |
                           |________USBReadAsync (Data)
Zitat:

Zitat von sirius
Wenn du die Größe kennst, dann nimm doch ein dynamisches Array (var Data:array of byte) und setze jedesmal vor dem Aufruf die Länge (setlength(x,Länge)).

Das klingt nach einer guten Vorgehensweise. Werde ich so machen. setlength kannte ich nicht.


Zitat:

Zitat von sirius
Musst du Data vorbelegen? Ich vermute nicht. Dann würde ich die Sache mit PDataFrame etc, ganz fallen lassen, sondern allein mittels synchronize arbeiten.

Okay. Das habe ich (logischerweise) auch noch nie gemacht. Verstehe den Ansatz auch nicht so ganz und finde kein einfaches Beispiel. Könntest Du evtl. anhand von dem Quellcode im Post #4 das kurz ganz grob skizzieren, damit ich einen Anfang habe? Steige mit dem synchronize trotz TFM nicht so ganz durch. Wäre echt nett :?

Zitat:

Zitat von sirius
Wie startest du den Thread?
Was sind das für Daten?

Einen Thread starte ich beispielsweise so:
Delphi-Quellcode:
    ReceiveCommandThread := TBulkReceiveCommandThread.Create( aPipeHandles[1][0],
                                                              pData,             //RxData,
                                                              @DSPMessage);
    ReceiveCommandThread.Suspended := FALSE;

    Ret := ReceiveCommandThread.WaitFor;
Die Daten sind halt Nutzdaten, welche auf die Applikation keinen Einfluss haben.

Viktorii 21. Feb 2008 12:26

Re: var-Parameter mit Thread nutzen
 
Kann mir noch jemand was zu den offen stehenden Fragen sagen?
Vielen Dank


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

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