Entwickler-Ecke

Grafische Benutzeroberflächen (VCL & FireMonkey) - Thread Terminate nicht aufrufbar


whitef - Sa 25.08.12 01:32
Titel: Thread Terminate nicht aufrufbar
hi,
hab da noch eine Frage zum Thread.
Es klappt alles soweit, bis auf das Terminate aus einer anderen Unit.


Ich hab über Delphi eine neue Unit (Klasse TThread) angelegt. Ich habe den Namen "MyThread" vergeben.
Das kam in der neuen Unit raus

Delphi-Quelltext
1:
2:
type
  MyThread  = class(TThread)


Jetzt möchte ich gerne in Unit1 auf meiner Form1 mittels button OnClick > MyThread.Terminate; ausführen um es in der Threadschleife abfragen zu können.
Deswegen habe ich in meiner neuen "Thread"-Unit namens "UnitMyThread" folgendes geändert:

Delphi-Quelltext
1:
2:
type
  TMyThread  = class(TThread)


und das hinzugefügt ("UnitMyThread"):

Delphi-Quelltext
1:
2:
public
  MyThread : TMyThread;


Das hier habe ich noch in "Unit1" hinzugefügt:

Delphi-Quelltext
1:
2:
3:
4:
5:
...
implementation

uses UnitMyThread;
...


Jetzt kann ich zwar in der "UnitMyThread" MyThread.Terminate; ausführen, aber ich möchte es ja von Unit1 aus ausführen.
Was hab ich den jetzt vergessen?

PS: Habt Nachsicht, schaut auf die Uhrzeit des Postings :-D


jaenicke - Sa 25.08.12 07:23

Wegen deines anderen Threads:
Vorsicht, wenn du noch FreeOnTerminate nutzt. Dann solltest du keinerlei Referenz auf den Thread behalten. Wenn der Thread sich selbst verwaltet, darfst du auch nicht versuchen auf das Threadobjekt zuzugreifen. Denn du weißt ja nie wann es weg ist.

Einzige Alternative, wenn du auf das Threadobjekt zugreifen willst:
Benutze nicht FreeOnTerminate, sondern gib den Thread auch manuell von außen frei nachdem er beendet wurde (WaitFor).

Zum Problem:
Unter public kannst du nur aus dem Objekt heraus zugreife, schreibe es stattdessen in eine Klassenvariable, dann kannst du von außen zugreifen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
public
  class var
    MyThread: TMyThread;

...

TMyThread.MyThread.Terminate;


whitef - Sa 25.08.12 09:25

Vielen Dank, nun kann ich auf Form1 TMyThread.MyThread.Terminate; aufrufen.

Aber da taucht das nächste Problem auf, ein EAccessViolation wird ausgelöst sofern man obigen Befehl ausführt:

Form1:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
procedure TForm1.Button1Click(Sender: TObject);
begin
if TMyThread.MyThread.ThreadRunning then begin
  TMyThread.MyThread.Terminate;                    ////// Hier liegt der Hund begraben... > EAccessViolation
  showmessage('terminiert');
end else
  showmessage('Zzt. kein thread läuft');
end;


UnitMyThread:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
function TMyThread.ThreadRunning:boolean;
begin
  result:=running>0;
end;

procedure TMyThread.Execute;
var i : Integer;
begin
inc(running);
Synchronize(Sync_Panels);
for i := 0 to (StrToInt(varThreadDateien) - 1do
  begin
      if not Terminated then begin
        ...
      end else break;
  end;
dec(running);
end;


Button1Click:

Quelltext
1:
...EAccessViolation...                    


jfheins - Sa 25.08.12 09:32

Also sofern das jetzt nicht bewusst als Singleton geplant ist würde ich auf die Klassenvariable verzichten.

Lege dir stattdessen ein privates Feld im Formular an. Also bei der Definition von Form1 sowas wie

Delphi-Quelltext
1:
2:
private
MyThread: TMyThread;

Und dann beim Erstellen des Threads:

Delphi-Quelltext
1:
MyThread := TMyThread.Create(..., ...);                    

(Die Variable Thread von vorher war ja lokal, da du jetzt von woanders drauf zugreifen möchtest, musst du sie im Scope "hoch stufen")
Und dann, wie gesagt, nicht FreeOnTerminate verwenden.


whitef - Sa 25.08.12 09:42

zum testen, ist alles in meinem aktuellen Projekt, später werde ich diese Thread Unit wieder soweit clearen, dass ich dass Grundgerüst bei anderen Projekten mitübernehmen kann.
Also ich will diesen Thread in eine extra Unit auslagern und nicht im Form1 deklarieren. Nur von Form1 aus aufrufen und beenden.


jfheins - Sa 25.08.12 10:03

Um das deklarieren einer Variable wirst du nicht herum kommen 8)

Im Gegenteil, siehe dein eigener Code:

Delphi-Quelltext
1:
2:
3:
4:
procedure TForm1.btSaveClick(Sender: TObject);
var i: Integer; TargetDir, datei0, datei1, dateien: String;
Thread: TMyThread; // <<<<<<< Na was ist das denn??
begin


Und jetzt verschiebst du einfach diese Deklaration in den private-Teil deines Formulars. Dann kannst du nämlich auch aus anderen Funktionen darauf zugreifen.
Also das deklarieren ist volkommen OOP konform und widerspricht nicht der Kapselung ;)


whitef - Sa 25.08.12 10:37

Danke!

Form1:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
...
uses
    ..., UnitMyThread;
...
  public
    MyThread: TMyThread;

implementation

procedure TForm1.btAbbrechenClick(Sender: TObject);
begin
MyThread := TMyThread.Create(..., ...);  
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
if MyThread.ThreadRunning then begin
  MyThread.Terminate;                    ////// Er liegt immer noch dort... > EAccessViolation
  showmessage('terminiert');
end else
  showmessage('Zzt. läuft kein thread');
end;


UnitMyThread:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
function TMyThread.ThreadRunning:boolean;
begin
  result:=running>0;
end;

procedure TMyThread.Execute;
var i : Integer;
begin
inc(running);
Synchronize(Sync_Panels);
for i := 0 to (StrToInt(varThreadDateien) - 1do
  begin
      if not Terminated then begin
        ...
      end else break;
  end;
dec(running);
end;


jfheins - Sa 25.08.12 10:47

Okay, weitere Fragen:
1. Hast du das FreeOnterminate := true; entfernt?
2. Klickst du auch brav auf den Button "btAbbrechen" bevor du auf den Button "Button1" klickst?
3. Wenn's immer noch Probleme gibt, zeige bitte etwas mehr Code.
MfG