Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - procedure variable, threads


Symbroson - Do 26.10.17 18:34
Titel: procedure variable, threads
Moin EE,
Ich brauche für ein Threading-Projekt eine Variable vom Typ procedure, sodass ich der nach belieben eine vorher definierte Prozedur (ohne Argumente) zuweisen kann.
Ich habe aber nur folgendes gefunden:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
interface
type TProcedure = procedure of object;

implementation

procedure foo; begin { ... } end;

procedure OnStart;
var func: TProcedure;
begin
  func := foo;
end;


Allerdings sagt mir Delphi bei der Zuweisung von foo auf func, dass 'Methodenzeiger' und 'Reguläre Prozedur' nicht kompatibel sind.

Nungut, Methodenzeiger klingt nach pointer, also
'func := @foo;'
Aber nein, TProcedure und Pointer sind ebenfalls nicht kompatibel.

Bei der suche nach dem ersten Fehler bin ich nur auf Codes gestoßen, bei denen die Argumente nicht übereinstimmen, aber ich habe ja gar keine...

zur Info: ich hab Delphi7

Hoffe, jemand kann mir aus der Klemme helfen :)
Ich möchte nämlich nicht für jede Prozedur eine extra Klasse anlegen, da das den Code unnötig in die Länge streckte (bei zurzeit bis zu 10 threading-Prozeduren...)


Nebenbei - weil's grad passt:
wenn ich zwei Prozeduren habe (1 und 2), und diese in zwei Threads ausführe, werden diese korrekt parallel ausgeführt.
Wenn ich aber einen Thread starte (egal ob mit CreateThread oder MyThread.Create), der prozedur1 ausführt und danach die andere prozedur2 einfach aufrufe, wird erst prozedur2 ausgeführt, und danach prozedur1 mit dem Thread.
Schließe ich daraus richtig, dass die Threads erst nach verlassen aller Prozeduren bzw Funktionen gestartet werden?
Kann man das ggf. umgehen und den Thread noch in einer Prozedur starten (um in derselben auf die beendigung aller Threads zu warten)
Sonst müsste ich zusätzlich Callback-Prozeduren anlegen...

Vielen Dank im Vorraus :)
Symbroson


Gammatester - Do 26.10.17 19:29

Laß das of object weg. Ich weiß nicht, ob es das ist, was Du willst, aber es funkioniert unetr Delphi6+.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
type TProcedure = procedure;

implementation

procedure foo;
begin
  showmessage('Hi');
end;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var func: TProcedure;
begin
  func := foo;
  func;
end;


Symbroson - Do 26.10.17 19:38

Tatsache...
irgendwas muss ich noch falsch gemacht haben - denn als ich das mal ohne 'of object' ausprobiert hatte, hat Delphi auch ne Fehlermeldung ausgespuckt.

Danke trotzdem ^^

Hat jmnd noch eine Erklärung für das Thread-Problem? ;)


GuaAck - Do 26.10.17 21:17

Hallo Symbosan,

Windows lässt einen Thread pausieren, nachdem er eine bestimmte Zeit (einige ms) gelaufen ist; dann lässt Windows den nächsten Thread laufen (im Prinzip...). Bei einem Rechner mit nur einem Core würde also der Hauptthread zunächst nur den Thread2 beauftragen, der läuft aber erst los, wenn die Zeit für den Hauptthread abgelaufen ist oder der freiwillig pausiert. Das ergäbe genau das von Dir beschrieben Verhalten.

Ich unterstelle, Du hast einen halbwegs modernen PC mit einer CPU, die mehrere Cores hat. Dann verstehe ich das Verhalten nicht, außer, wenn die anderen Cores im Hintergrund noch viel andere Arbeit haben.

In jedem Fall ist es aber immer ungewiss, wie die Bearbeitungsreihenfolge ist. Wenn die Reihenfolge der Abarbeitung wichtig ist, dann musst Du das selbst regeln, z. B. mit Semaphoren oder Events.

Viel Erfolg,
Gruß
GuaAck


Symbroson - Do 26.10.17 21:20

Gibt es denn eine Funktion mit dem man den Hauptthread dazu bringt, 'freiwillig' zu pausieren?


jaenicke - Fr 27.10.17 06:12

user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
wenn ich zwei Prozeduren habe (1 und 2), und diese in zwei Threads ausführe, werden diese korrekt parallel ausgeführt.
Wenn ich aber einen Thread starte (egal ob mit CreateThread oder MyThread.Create), der prozedur1 ausführt und danach die andere prozedur2 einfach aufrufe, wird erst prozedur2 ausgeführt, und danach prozedur1 mit dem Thread.
Wie sieht denn der Quelltext dazu grob aus? Und wie lange laufen die Prozeduren?

user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
Gibt es denn eine Funktion mit dem man den Hauptthread dazu bringt, 'freiwillig' zu pausieren?
Ja, TThread.Yield:
http://docwiki.embarcadero.com/Libraries/Tokyo/de/System.Classes.TThread.Yield

user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
irgendwas muss ich noch falsch gemacht haben - denn als ich das mal ohne 'of object' ausprobiert hatte, hat Delphi auch ne Fehlermeldung ausgespuckt.
Zur Erklärung:
"of object" heißt, dass du eine Methode einer Klasse referenzieren möchtest. Ohne alles ist es eine freie Prozedur oder Funktion. Und mit "reference to" vorne dran ist es eine anonyme Methode (ab Delphi 2009).


Symbroson - Fr 27.10.17 07:02

Zitat:
irgendwas muss ich noch falsch gemacht haben

Ich glaube ich hatte noch versucht eine Prozesur an eine funktion als argument zu übergeben. Ich weiß jetzt aber nicht genau ob ea das war...


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
procedure call(foo:TProcedure);
begin foo;
end;

procedure greet;
begin showmessage('hello');
end;

procedure OnStart;
begin call(greet);
end;



der code ist so minimalistisch wie möglich gehalten. Hab mal versucht alle angesprochenen Varianten reinzupacken.
Bitte Entschuldigt mögliche Syntaxfehler - musste es ausm Kopf irgendwie aufschreiben ohne es testen zu können :/


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:
67:
68:
69:
type TProcedure = procedure;
    TTestThread1 = class(TThread)
        procedure Execute; override;
        call: TProcedure;
    end;

var running: integer;

implementation

procedure TTestThread1.Execute;
begin call;
end;

procedure call1;
var i: integer;
begin
    inc(running);
    for i := 0 to 4 do begin
        Memo1.Lines.Add('Hello 1');
        sleep(500);
    end;
    dec(running);
end;

procedure call2;
var i: integer;
begin
    inc(running);
    for i := 0 to 4 do begin
        Memo2.Lines.Add('Hello 2');
        sleep(500);
    end;
    dec(running);
end;

procedure TForm1.Button1Click(Sender:TObject);
var id1, id2;
    thread1, thread2: TTestThread;
begin
        //Variante 1
    CreateThread(nil0, TFNThreadStartRoutine(@call1), nil0, id1);
    CreateThread(nil0, TFNThreadStartRoutine(@call2), nil0, id2);
    
        //Variante 2
    CreateThread(nil0, TFNThreadStartRoutine(@call1), nil0, id1);
    call2;

        //Variante 3
    thread1 := TTestThread.Create;
    thread1.call := call1;

    thread2 := TTestThread.Create;
    thread2.call := call2;

        //Variante 4 - mit Yield
    running := 0;

    thread1 := TTestThread.Create;
    thread1.call := call1;
    Yield;

    thread2 := TTestThread.Create;
    thread2.call := call2;
    Yield;

    while running > 0 do;
    showmessage('done');
end;


Symbroson - Fr 27.10.17 18:34

Ich konnte das jetzt endlich mal ausprobieren. Das mit dem Yield funktioniert bei mir nicht. Die while-Schleife danach blockiert die Threads trotzdem.
Und 'TThread.Yield' gibt es bei mir nicht, nur 'Yield' ohne Namespacename davor ... :/


jaenicke - Fr 27.10.17 21:22

In den Beispielen greifst du aus dem Thread auf deine visuellen Komponenten zu. Das darfst du nicht. Diese Zugriffe müssen immer im Kontext des Hauptthreads ausgeführt werden! Dass das dann Probleme macht, ist klar.

Jegliche Zugriffe auf z.B. ein Memo müssen synchronisiert werden. Wenn du deine Thread-Klasse über Datei --> Neu anlegst, wird auch eine entsprechende Warnung inkl. Beispiel als Kommentar eingebaut.


Symbroson - Fr 27.10.17 21:27

Also Probleme gab es dadurch bei mir bisher nicht. Ich nehme außerdem deswegen zwei memos - eins für jeden Thread. Dadurch sollte nichts kaputt gehen.
Ist ja auch nur ein Beispiel...


jaenicke - Fr 27.10.17 23:50

Doch, das ist auch dann ein Problem. Zugriffe aus Threads auf visuelle Komponenten können an ganz anderen Stellen massive Probleme machen. Das merkst du in so einem simplen Beispiel vermutlich nicht unbedingt.

TTestThread sollte jedenfalls die Prozedur direkt im Konstruktor mitbekommen, damit der Thread nicht schon läuft, wenn du gerade erst die Prozedur zuweist.

Und das Hauptproblem:
Du lässt eine Schleife im Hauptthread auf die Threads warten. Wenn die aber nichts anderes tun als auf die Memos zuzugreifen, dann merkst du nichts davon, dass die Threads laufen bis diese Schleife im Hauptthread fertig ist. Denn das Fenster kann gar nicht neu gezeichnet werden, hat also bis deine while-Schleife fertig ist noch den alten Inhalt.
Lass die Schleife einfach mal weg, dann wirst du die Ausgaben der Threads auch vorher sehen.