Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Thread im Oncreate der Form falsch?


ALF - Fr 30.07.10 20:06
Titel: Thread im Oncreate der Form falsch?
Hi, dazu ein paar kurz Infos.
Die Formen werden zur Laufzeit als MDICHILD erstellt.
Der Thread als solches funktioniert innerhalb der Form.
Mein Problem ist, ich benötige die Daten aber schon wenn die Form erstellt wird!
Also meine Idee, im Oncreate-ereignis der Form. Mh.... und genau da will, oder kann es nicht funktionieren.
Die Daten werden vom Thread richtig gelesen, wenn es aber zum Abschluss der Procedure Synchronize kommt, kracht es mit EAccessViolation, schreiben von Adresse usw., obwohl die Daten in der Newform übertragen wurden.
Das ist die Procedure die Synchronize aufruft:

Delphi-Quelltext
1:
2:
3:
4:
procedure TScanThread.UpdatePeaksTh;
begin
  FNform.UpdatePeaks(FpeaksL, FpeaksR);
end;//<------und hier krachts!!!

als wenn die Procedure nicht mehr zurückfindet. Kann das sein??
Der Aufruf aus dem Thread erfolgt so:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
procedure TScanThread.Execute;
var
  .....
  .....
begin
     OutputDebugString('ich arbeite');//ist nur für mich
     ....
     ....
     while (buflen > 0and (not terminated) do
     begin
         ..... //jedemenge berechnen
         .....
     end;
     OutputDebugString('Daten können abgeholt werden'); // ist nur für mich
     Synchronize(UpdatePeaksTh);
     ....
     ....
     OutputDebugString('ich beende mich'); // ist nur für mich
     Terminate;
end;

Beim kontollieren mit dem EreignisProtokoll kommt es bis 'Daten können abgeholt werden'
Eine Rückkehr der Procedure gibt es nicht!
Jemand ne Idee ob es so ist, wie ich vermute!
Wenn ja, was muss ich den da noch beachten?
Oder Liegt der Fehler ganz wo anders?
Gruss Alf


jaenicke - Fr 30.07.10 20:13

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
Mein Problem ist, ich benötige die Daten aber schon wenn die Form erstellt wird!
Wie stellst du dir das vor? Das dauert schließlich kurz bis der Thread fertig ist. Und da ist dein OnCreate schon vorbei, du wirst die Daten also nie beim Erstellen selbst schon haben.

Wie erstellst und startest du denn den Thread? Kann es sein, dass du FreeOnTerminate benutzt? Wenn du da nicht aufpasst...
Und wo weist du die Formularvariable dem Thread zu? Im Konstruktor oder erst danach? (Wenn danach, musst du natürlich pausiert starten. :idea:)

Ja, und dann ist noch die übliche Frage nach dem Ergebnis des Debuggers? Ist die Formvariable in der Zeile erreichbar?

Und natürlich: An welcher Adresse kommt denn die Zugriffsverletzung? Wenn die sehr klein ist, hast du nen Nullpointer, dann vermutlich die Formularvariable.


ALF - Fr 30.07.10 20:56

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Wie stellst du dir das vor? Das dauert schließlich kurz bis der Thread fertig ist. Und da ist dein OnCreate schon vorbei, du wirst die Daten also nie beim Erstellen selbst schon haben.

Beim Debuggen gebrüft, sind die Daten(es sind Arrays in der Form) schon gefüllt!
Also auch da, oder?
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Wie erstellst und startest du denn den Thread? Kann es sein, dass du FreeOnTerminate benutzt? Wenn du da nicht aufpasst...
Und wo weist du die Formularvariable dem Thread zu? Im Konstruktor oder erst danach? (Wenn danach, musst du natürlich pausiert starten. :idea:)

mh... MainForm=MDIFORM

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
       if OpenDialog1.Execute then
       begin
            ....
            ....
           NewForm:= TForm3.Create(self);
       end;

TForm3:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
procedure TForm3.FormCreate(Sender: TObject);
begin
    ....
    ....
    TScanThread.Create(mychan1, xzoom, self);
end;
.....
.....
//hier der Aufruf um die Daten abzuholen
//wird auch komplett durchgeführt
procedure TForm3.UpdatePeaks(pL, pR: array of smallint);
var i: integer;
begin
    for i:= low(pL) to high(pL) do
    begin
  wavebufL[i]:= pL[i];
  wavebufR[i]:= pR[i];
    end;

   timer1.Enabled:= true;
end;

Constructor des Threads

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
constructor TScanThread.Create(decoder: HSTREAM; zoom: integer; Nform: TForm3);
begin
  inherited create(false);
  Priority:= tpNormal;
  FreeOnTerminate:= true;
  FDecoder:= decoder;
  FZoom:= zoom;
  FNform:= NForm;
end;

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Ja, und dann ist noch die übliche Frage nach dem Ergebnis des Debuggers? Ist die Formvariable in der Zeile erreichbar?

Und natürlich: An welcher Adresse kommt denn die Zugriffsverletzung? Wenn die sehr klein ist, hast du nen Nullpointer, dann vermutlich die Formularvariable.

Ja die Formvariable ist erreichbar.Die Arrays von TFORM3 werden ja gefüllt mit den Daten vom Thread. es Kracht ja auch beim 'end;' nicht bei 'FNform.UpdatePeaks(FpeaksL, FpeaksR)';
Also nach beenden von 'procedure TForm3.UpdatePeaks(pL, pR: array of smallint);'

1. Meldung: Z.verletzung bei Adresse 00401c19 in Modul Projekt.exe Schreiben von Adresse 000000001
2.Meldung EInvalidPointer ungültige Zeigeroperation!

Also das alte leiden, nicht einhalten von Grundsätzlichen Dingen! richtig?
Welche Möglichkeit habe ich aber dann, die Daten rechtzeitig zur verfügung zu haben, wenn nicht im Oncreate zu erstellen geht?
Gruss Alf


jaenicke - Sa 31.07.10 08:58

Vom Grundsatz her ist das schon ok. Nur beim Thread nicht ganz.
user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
constructor TScanThread.Create(decoder: HSTREAM; zoom: integer; Nform: TForm3);
begin
  inherited create(false);
  Priority:= tpNormal;
  FreeOnTerminate:= true;
  FDecoder:= decoder;
  FZoom:= zoom;
  FNform:= NForm;
end;
Du sagst, dass der Thread sofort loslaufen soll im inherited, weist die Formularvariable aber erst danach zu. Zudem sagst du, dass er sich sofort zerstören soll, wenn er fertig ist. Was aber, wenn er fertig ist bevor die Initialisierung fertig ist? Crash. ;-)

Deshalb mach es lieber sauber:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
private
  MyThread: TScanThread;
...
//FormCreate:
MyThread := TScanThread.Create(...); // nicht FreeOnTerminate und nicht sofort loslaufen
MyThread.MyForm := ...; // geht natürlich auch im Konstruktor, soll nur zeigen, dass es auch so geht
MyThread.Resume;

//FormDestroy:
MyThread.Terminate; // wenn der Thread in einer Schleife mit "while not Terminated" läuft
MyThread.WaitFor; // oder so ähnlich, glaub es hieß so
MyThread.Free;


ALF - Sa 31.07.10 10:10

Hi, und guten Morgen!
Jo vom Prinzip verstanden, die Erklärung ok.
Aber!! Ich vergass zu erwähnen das der Thread in einer anderen Unit liegt!
Also fang ich an alles zu modifizieren....., wenn ich es so machen will, soll.
Aber nun:
Egal wie ich es nun ansetzte. Entweder die Units kreuzen sich, was auch nicht geht, oder beim zuweisen von: newthread: TScanThread; wird TScanthread nicht gefunden, oder die Procedure von TForm3 zum abholen der Daten ist nicht bekannt!

Egal was ich nun versuche, es kommt immer eine Stelle die nicht zusammen passen will. Ich hab einfach keine Idee mehr :nixweiss:
Selbst die Tuts, und alles was ich in den Foren gefunden habe, geben mir darauf keine Antwort!
Und alles nur, weil ich die Daten am Anfang schon benötige!

Gruss Alf


jaenicke - Sa 31.07.10 10:17

user profile iconALF hat folgendes geschrieben Zum zitierten Posting springen:
oder die Procedure von TForm3 zum abholen der Daten ist nicht bekannt!
Das ist eigentlich auch nicht der richtige Weg. Stell in deinem Thread ein Event zur Verfügung, das dich benachrichtigt, wenn die Daten da sind, und hole die dann vom Formular aus ab. Der Thread sollte sowieso nicht wissen wohin die Daten sollen.

Denn der Thread hat nur den Sinn die Daten zu holen und bereitzustellen, mehr nicht.

Du kannst auch OnTerminate benutzen, aber mit einem eigenen Event hast du (für die Zukunft) auch die Möglichkeit zwischendrin Rückmeldungen zu geben.

Die Daten kannst du dann im Thread per Property bereitstellen. Da du den Aufruf des Events im Thread synchronisierst, kann der Hauptthread auch in dem Event darauf zugreifen. (Du kannst natürlich dem Event auch Parameter mitgeben, Sender sollte sowieso dabei sein.)


ALF - Sa 31.07.10 17:26

Ok habe nun alles neu gemacht, kann also jetzt unabhängig den Thread jetzt so aufrufen,
Dabei übergebe ich aber keine Form mehr!
Rückgabewert ist auch vorhanden

Delphi-Quelltext
1:
2:
3:
4:
NewThread:= TscanThread.Create(...)

//Abfrage ob fertig
if NewThread.Binfertig then   //mache was

Schön, weiss nun aber immer noch nicht wie es weiter geht! Wo kommt die Abfrage hin?
mit dem Event naja, habe mir einiges angeschaut. Diese sind aber so Projektspeziell, das ein Rückschluss, auf mein Projekt, irgendwie nicht passt!?
Selbst wenn ich mir, nach den Vorbildern, ein Event zusammenschreibe, könnte ich damit erst mal nichts Anfangen, da ich nicht weiss wo setzte ich es ein.
Ich bekomme irgendwie nicht den zusammenhang hin, bezogen auf mein oncreate Problem!

Gruss Alf


jaenicke - Sa 31.07.10 17:53

Also, als Beispiel:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
// im Thread:
public
  property Array1: ...
  property Array2: ...
end;

// im Formular:
private
  FMyThread; TMyThread;

FMyThread := ...
FMyThread.OnTerminate := MyThreadTerminate;


procedure TMyForm.MyThreadTerminate(Sender: TObject);
begin
  FMyThread.Array1 ...
end;
Oder du erstellst dir nen Event:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
// im Thread:
TMyUpdatePeaksEvent = procedure(Sender: TObject; pL, pR: array of SmallInt) of object;

...

public
  OnUpdatePeaks: TMyUpdatePeaksEvent read FOnUpdatePeaks write FOnUpdatePeaks;

...

// und im synchronisierten dann:
if Assigned(FOnUpdatePeaks) then
  FOnUpdatePeaks(Self, MyArray1, MyArray2);

// und im Formular:
private
  FMyThread; TMyThread;

FMyThread := ...
FMyThread.OnUpdatePeaks := MyThreadUpdatePeaks;


procedure TMyForm.MyThreadUpdatePeaks(Sender: TObject; pL, pR: array of SmallInt);
begin
  pL...
end;
Erstellen kannst du den Thread ruhig in OnCreate des Formulars und freigeben entsprechend später in OnDestroy.


ALF - Sa 31.07.10 18:21

Ja, an so was habe ich mich versucht und bin dann da gescheitert!
In der Unit Thread:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
type TMyUpdatePeaksEvent = procedure(Sender: TObject; pL, pR: array of SmallInt) of object;

type TScanThread = class(TThread)
   private
       Fdecoder: HSTREAM;
       Fzoom: integer;
       Fsec: integer;
       FpeaksL: array of smallint;
       FpeaksR: array of smallint;
       //FNform: TForm;
       FBinfertig: Boolean;
       FOnUpdatePeaks: array of smallint;///muss hier sein oder wo????
       //FDatenholen: TDatenholen;
    protected
       procedure Execute; override;
       //procedure DoTerminate; override;
       //procedure UpdatePeaksTh(peaksL, peaksR: array of smallint);
       procedure SetBinfertig(const Value: Boolean);
    public
      constructor Create(decoder: HSTREAM; zoom: Integer; sec: integer);
      property Binfertig: Boolean read FBinfertig default False;
      property OnUpdatePeaks: TMyUpdatePeaksEvent read FOnUpdatePeaks write FOnUpdatePeaks;
  end;

jetzt bekomme ich, auch vorhin wo ich es mit meinem eigenem event TDatenholen versucht habe, ne Fehlermeldung:
[Fehler] Unit2.pas(29): Inkompatible Typen: 'dynamic array' und 'TMyUpdatePeaksEvent' oder ebend TDatenholen. Hatte ich es also wieder verworfen!
Weiss zwar was die FehlerMeldung bedeutet, weiss aber nicht was ich ändern muss!!

Sorry für C&P

Gruss Alf


jaenicke - Sa 31.07.10 18:25

Naja, fast...

Delphi-Quelltext
1:
FOnUpdatePeaks: TMyUpdatePeaksEvent;                    
Schließlich hast du die Property auch mit diesem Typ deklariert...


ALF - Sa 31.07.10 19:08

Ich verzweifle :cry:
EAccessViolation: schreiben von Adresse 000000001
Aber nur in der IDE!

Starte ich das ohne, keine Fehler!?(noch nicht auf ein anderen Rechner gestestet)

Habe zum Test mal die Freigabe der dynamischen Arrays im Thread auskommentiert, oops keine Fehler mehr, auch in der IDE!
Habe ich die zu früh freigegeben? Hier mal Thread.execute.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
procedure TScanThread.Execute;
var 
  PeakBuf: array[0..176400of integer;  //=1sekunde daten
  .....
  .....
begin
  inherited;
  try     

     setlength(FpeaksL, 2600);  //max witdh |muss noch genau bestimmt werden;
     setlength(FpeaksR, 2600);  //ist nur zum Test
     .....
     .....
     OutputDebugString('Daten können abgeholt werden'); // ist nur für mich
     FBinfertig:= True;
     Synchronize(UpdatePeaksTh);
finally

    //setlength(FpeaksL, 0);
    //setlength(FpeaksR, 0);
    //FpeaksL:= nil;
    //FpeaksR:= nil;

    OutputDebugString('ich beende mich'); // ist nur für mich
    Terminate;
end;


Gruss Alf


jaenicke - Sa 31.07.10 19:13

In welcher Zeile tritt der Fehler denn auf? Kann es sein, dass du von außen noch auf ein Array im Thread zugreifst?

Wie sind die Arrays denn deklariert? Mich irritiert das := nil etwas. :gruebel:


ALF - Sa 31.07.10 19:21

An welcher Stelle, wie beim Anfang!

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
procedure TScanThread.UpdatePeaksTh;//(FpeaksL, FpeaksR);
begin
if Assigned(FOnUpdatePeaks) then
  FOnUpdatePeaks(Self, FpeaksL, FpeaksR);

end;//<----hier krachts

nach dem abholen der Daten.

Schau mal 2 Posts weiter hoch von mir, da steht wie TScanThread erstellt wird mit den Arrays:
FpeaksL: array of smallint;
FpeaksR: array of smallint;

Gruss Alf


jaenicke - Sa 31.07.10 19:31

Das hatte ich schon so verstanden, aber da dann der Code nicht viel Sinn macht, habe ich nochmal nachgefragt. ;-)
Es reicht schon die Länge auf 0 zu setzen.

Ja, so sehe ich an dem geposteten Code erstmal auch nix außergewöhnliches. FreeOnTerminate und sowas hattest du ja deaktiviert, oder?


ALF - Sa 31.07.10 20:21

Jo, FreeOnTerminate:= false, sonst krachts auch an der Stelle!!
Sobald ich,
setlength(FpeaksL, 0);
setlength(FpeaksR, 0);
wieder drin hab krachts.

Also muss ich was anderes machen für die Freigabe oder?

Nur komischer weise passiert das nur wenn der Thread im oncreate aufgerufen wird!

Im Programm selber (TForm3) brauche ich den Thread ja auch, also nach create newform;
Da kann es so stehen, setlength(FpeaksL, 0) und nichts passiert!??? Selbst FreeOnTerminate:= True, macht da nix.
Fragen über Fragen, ich weiss, aber wenn man als Anfänger mit sowas (wiedersprüchliches) konfrontiert wird :?

Ich bedanke mich für die super Aussdauer mit mir :zustimm:
Hab auf alle Fälle mehr gelernt, als xTage in allen Foren suchen(was ich ja gemacht habe) und nix finden :D

Ich lass die Freigabe erst mal weg, sonst komm ich nie weiter!!
Edit1: Freigabe der Arrays muss gemacht werden, sonst StackOverFlow :mrgreen:(dyn. Arrays)
Edit2:Feststellung!! Solange die Form in eine Initialisierung steckt kann man keinen Thread starten :!: :?: (Kann man schon, aber nicht mit dyn.Arrays)

Gruss Alf