Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Multithread-Applikation


walter_b - Sa 26.07.08 12:08
Titel: Multithread-Applikation
Hallo Leute

Ich würde gerne aus einem normalen Programm ein Multithread-Programm machen. Das Problem ist jetzt nur, dass ich nicht so recht mit dem Thread zu Gange komme. Ich habe schon in der Hilfe gelesen und auch sonst Sachen angeguckt, aber irgendwie funktioniert das bei mir nicht so recht.

Zuerst einmal: Welche Uses sind notwendig dafür? Reicht Classes?

Hier habe ich mal ein Codesample aus einem etwas älteren Posting von Sprint (http://www.delphi-forum.de/viewtopic.php?p=230946#230946):


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:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private-Deklarationen }
   MyThread: TThread;
  public
    { Public-Deklarationen }
  end;

  TMyThread = class(TThread)
  protected
    procedure Execute; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TMyThread }

procedure TMyThread.Execute;
begin
  while not Terminated do
  begin
    MessageBeep(UINT(-1));
    Sleep(1000);
  end;
end;

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
begin
  if not Assigned(MyThread) then
  begin
    MyThread := TMyThread.Create(True);
    with MyThread do
    begin
      FreeOnTerminate := True;
      Resume;
    end;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  if Assigned(MyThread) then
    MyThread.Terminate;
end;

end.


Von der Struktur her sieht für mich das alles ganz Interessant aus, da es relativ einfach ist. Aus diesem Grund habe ich es auch so versucht, nachzubauen, allerdings kommt bei mir immer das Problem, dass ich in einer Konsolenapplikation arbeite und folglich am Anfang die Deklaration mit dem TForm nicht stimmt. Allerdings weiss ich nicht, wie ich den Thread jetzt umdeklarieren muss, sodass es bei mir funktioniert. Wäre euch dankbar, wenn mir jemand ein einfaches Beispiel machen könnte, um dies zu veranschaulichen.


Gausi - Sa 26.07.08 12:21

Arbeite am Besten mal das Tutorial zu Threads [http://www.michael-puff.de/Developer/Delphi/Tutorials/] von user profile iconLuckie durch. ;-)


walter_b - Sa 26.07.08 12:46

Ich habe das Tutorial nochmal durchgelesen und für mich etwas erstellt, was soweit auch funktioniert:


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:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
type
  TThreadFunc1 = function(Parameter: Pointer): Integer;
  TThreadFunc2 = function(Parameter: Pointer): Integer;
  TThreadFunc3 = function(Parameter: Pointer): Integer;
  TThreadFunc4 = function(Parameter: Pointer): Integer;


function ThreadFunc1(tp: Integer): Integer;
var
I:integer;
begin
  for I:=0 to 100 do
  Writeln('tschau');
end;

function ThreadFunc2(tp: Integer): Integer;
var
I:integer;
begin
  for I:=0 to 100 do
  Writeln('bye');
end;

function ThreadFunc3(tp: Integer): Integer;
var
I:integer;
begin
  for I:=0 to 100 do
  Writeln('cya');
end;

function ThreadFunc4(tp: Integer): Integer;
var
I:integer;
begin
  for I:=0 to 100 do
  Writeln('buybuy');
end;


procedure RunThread;
var
  tp : Integer;
  Thread : THandle;
  ThreadID : Cardinal;
  ExitCode : Cardinal;
begin
  Thread := BeginThread(nil, 0, @ThreadFunc1, 0, 0, ThreadID);
  Thread := BeginThread(nil, 0, @ThreadFunc2, 0, 0, ThreadID);
  Thread := BeginThread(nil, 0, @ThreadFunc3, 0, 0, ThreadID);
  Thread := BeginThread(nil, 0, @ThreadFunc4, 0, 0, ThreadID);
  // Warten, bis es fertig ist.

  writeln('hallo');
end;



[...]

//Hauptprogramm
begin
  RunThread;
end.



Hier funktioniert es soweit, nur weiss ich nicht, wie ich das mit dem Warten anstellen soll, dass auch gewartet wird, bis der Thread fertig ist. Mit ThreadFunc4.WaitFor meldet er "not enough actual Parameters". Und wenn ich die Funktion aus dem Tutorial nehme (WaitForSingleObject(Thread, INFINITE); ), dann meldet er mir "undeclared identifier "WaitforSingleObject. Wo liegt der Hund begraben?


walter_b - So 27.07.08 18:24

Alles funktioniert soweit, nur habe ich noch ein letztes Problem. Bei mir laufen im Moment 4 Threads parallel und alle kommen ziemlich zeitgleich zu einem Ende. Jedoch möchte ich jetzt, falls in einem bestimmten Thread gewisse Gegebenheiten gegeben sind, alle 4 beendet werden. EndThread funktioniert bei mir irgendwie nur auf denjenigen, in welchem ich es schreibe. Kann mir jemand weiterhelfen? Wie kann ich die 4 beenden? Das Programm sollte jedoch noch nicht ganz beendet werden, sondern nur diese Threads.


Tilo - Mo 28.07.08 08:08

TThread.Terminate setzt eine boolsche Variable(->Terminated oder so). Diese Variable brauchst Du im betreffenden Thread nur regelmäßig abfragen. Ist die Variable gesetzt (->True) dann beende den Thread.


Yogu - Mo 28.07.08 11:07

user profile iconwalter_b, deine Variante mag ja auch funktionieren, aber wir arbeiten hier mit Delphi, und deshalb ist objektorientierte Programmierung vorzuziehen.

Über Datei > Neu > Thread erstellt Delphi dir ein Grundgerüst eines Threads. Das kannst du jetzt ausbauen, deine eigenen Wünsche einbauen. Wie man einen solchen Thread beendet, hat user profile iconTilo ja schon geschrieben.


walter_b - Mo 28.07.08 14:25

Mich hauts jetzt dann gleich aus den Socken... Wenn ich im Thread etwas in einen String speichern will, kommt immer ein Error: "... raised Exeption class EAccessViolation with message 'Access violation at address ...".

z.b. bei dieser Stelle


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
var
  s: string;
  a: array of byte;

begin
 a[0]:=33;
 s[1]:=Chr(a[0]);
end;



Ich raffs nicht. Wenn ich das genau gleich ausserhalb des Threads mache, funktioniert es, innerhalb jedoch nicht. Warum das?


Tilo - Mo 28.07.08 17:24

Ist das ThreadObjekt zu diesem Zeitpunkt schon initialisiert?
Wie greifst Du auf den String zu(Über eine Methode des Threads oder der Haupt-Unit)?


walter_b - Mo 28.07.08 17:53

Ja, das ThreadObjekt ist initialisiert und wird zu dem Zeitpunkt auch ausgeführt. Auf den String greife ich nur in dem Thread zu, da der String auch nur in dem Thread als lokale Variable definiert ist.


Tilo - Mo 28.07.08 20:30

Hast Du dem Array auch Speicher zugewiesen?
z.B. mit setlength()?


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
var
  s: string;
  a: array of byte;

begin
 setlength(a,1);
 a[0]:=33;
 s[1]:=Chr(a[0]);
end;


walter_b - Mo 28.07.08 23:07

Jep, der Array wurde festgelegt. Der String ebenfalls.


Delete - Mo 28.07.08 23:22

Für die, die es interessiert, hier wurde ihm schon eine Lösung genannt:
http://www.delphipraxis.net/topic139323_mehrere+threads+auf+einmal+beenden.html