Entwickler-Ecke

Free Pascal - Thread möglichst elegant


BenBE - Di 26.01.16 11:41
Titel: Thread möglichst elegant
Hi,

heut von mir mal in der etwas ungewöhnlicheren Sparte ein Post.

Geht drum, ich habe eine Routine (wenige Zeilen), die auf Grund der darin verwendeten API zwingend auf einem eigenen Thread aufgerufen werden muss.

Für diese Routine muss ich 2 Parameter (Passen jeweils in einen Integer) übergeben.

Der startende Thread (in meinem Fall der Main Thread), soll auf den aufgerufnen Thread warten. Also in etwa:


Vereinfachter Pseudocode:
1:
2:
3:
4:
5:
6:
7:
{
    auto thread = NEW_THREAD( [foo, bar]() {
        // ... Do some stuff that requires being run in the separate thread ...
    } );
    thread.start();
    thread.waitForExit();
}


Geht das auch irgendwie, ohne erst groß TThread komisch ableiten zu müssen?

Ach ja: Geht mir nicht um Performance, sondern kurze, übersichtliche Schreibweise.


jaenicke - Di 26.01.16 16:19

Ich würde TTask verwenden, dieses Beispiel hatte ich dafür schon mal irgendwo gepostet:

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:
uses
  System.Threading;

var
  Tasks: array[0..1of ITask;
  a, b, c, d: Integer;
begin
  a := 42;
  b := 100;
  Tasks[0] := TTask.Create(
    procedure()
    begin
      Sleep(1000);
      c := a + b;
    end);
  Tasks[0].Start;
  Tasks[1] := TTask.Create(
    procedure()
    begin
      Sleep(3000);
      d := b - a;
    end);
  Tasks[1].Start;
  TTask.WaitForAll(Tasks);
  ShowMessage(IntToStr(c) + ' - ' + IntToStr(d));


BenBE - Di 26.01.16 16:47

Irgendwie meckert FPC bei mir bei System.Threading rum (";" erwartet, aber "." gefunden). Unit wird mit {$mode objfpc}{$H+} übersetzt, auch wenn das nicht das Problem sein dürfte.

Ansonsten ist das syntaktisch aber schon genau, wonach ich gesucht hatte :)

BTW: Lazarus 1.4.4 / FPC 2.6.4 in case it matters here.


jaenicke - Di 26.01.16 18:00

Dann kann Lazarus das wohl nicht...
Vielleicht heißt die Unit aber auch nur anders. Das Beispiel stammt aus Delphi, ich hatte die Sparte übersehen.


Martok - Di 26.01.16 19:35

Warum TThread.CreateAnonymousThread noch keine Nestedprocvars kann ist mir nicht klar, wenn das drin wäre wär's die Antwort. So muss man das kurz selber bauen (das was du suchst ist TNestedThread, der generic steht da nur weil ich mal wissen wollte wie die Syntax dann aussieht):

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:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
unit Unit1;

{$mode objfpc}{$H+}
{$ModeSwitch nestedprocvars}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

  TNestedThread = class(TThread)
  public type
    TRunFunc = procedure is nested;
  private
    FFunc: TRunFunc;
  protected
    procedure Execute; override;
  public
    constructor Create(Func: TRunFunc);
  end;

  generic TDataThread<GData> = class(TThread)
  public type
    TRunFunc = procedure (Data: GData);
  private
    FData: GData;
    FFunc: TRunFunc;
  protected
    procedure Execute; override;
  public
    constructor Create(Data: GData; Func: TRunFunc);
  end;


var
  Form1: TForm1;

implementation

{$R *.lfm}

procedure ThrdMethod(I: PInteger);
begin
  I^:= 1337;
end;

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
var
  x, y: integer;

  procedure ThrdMethodN();
  begin
    X:= 42;
    Y:= 23;
  end;

begin
  TNestedThread.Create(@ThrdMethodN).WaitFor;
  ShowMessageFmt('x: %d, y: %d', [x, y]);

  (specialize TDataThread<PInteger>).Create(@X, @ThrdMethod).WaitFor;
  ShowMessageFmt('x: %d, y: %d', [x, y]);
end;

{ TNested }

procedure TNestedThread.Execute;
begin
  FFunc();
end;

constructor TNestedThread.Create(Func: TRunFunc);
begin
  inherited Create(true);
  FreeOnTerminate:= true;
  FFunc:= Func;
  {%H-}Resume;
end;


{ TDataThread }

procedure TDataThread.Execute;
begin
  FFunc(FData);
end;

constructor TDataThread.Create(Data: GData; Func: TRunFunc);
begin
  inherited Create(true);
  FreeOnTerminate:= true;
  FData:= Data;
  FFunc:= Func;
  {%H-}Resume;
end;

end.

WaitForMultiple etc. überlasse ich mal als einfache Übung dem Leser ;)

Edit: Oh, noch was: in den Mainthread zurück kommst du dann per TThread.Synchronize und .Queue, aber der Deadlock da drin wenn man das im Code oben tut dürfte offensichtlich sein. Die Variante für Nested wäre dann auch noch zu bauen.


BenBE - Mi 27.01.16 13:17

Ist zwar nicht ganz so schön in-lambda-line, wie ich mir das erhofft hab, aber die TNestedThread-Klasse von Martok funzt wie gewünscht. Und Anfang der Methode ist grad noch so akzeptabel ;-)