Autor Beitrag
Bergmann89
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 1742
Erhaltene Danke: 72

Win7 x64, Ubuntu 11.10
Delphi 7 Personal, Lazarus/FPC 2.2.4, C, C++, C# (Visual Studio 2010), PHP, Java (Netbeans, Eclipse)
BeitragVerfasst: Di 05.02.13 21:31 
Hey Leute,

ich hab heute mal ein etwas spezielleres Problem. Und zwar bräuchte ich sowas ähnliches wie Delegaten (von C#) in Delphi. Der Hintergrund ist folgender: Ich habe einen ResourcenLoader, der asynchron Resourcen läd, die das Programm benötigt. Der ResourcenLoader bekommt ein Callback mit, mit dem das Objekt, das die Resource anfordert darüber informiert wird, dass die Resource geladen ist. Die Anfragen an den ResourcenLoader müssen nicht aus dem MainThread kommen, sondern können von jedem beliegen Thread abgesetzt werden. Nach dem asynchronen Laden wird das Callback aber im MainThread ausgeführt, da es mit Synchronize aufgerufen wurde. Ich hätte es gern, dass das Callback in dem Thread ausgeführt wird, der die Anfrage an den ResourcenLoader geschickt hat. Die Frage ist nun, wie man sowas am besten löst. Am liebsten hätt ich am Ende sowas wie SynchronizedCallback(const aThreadID: THandle; const aCallback: TCallback); Sodass ich im Thread nicht mehr synchronisieren muss.
Was auch noch gehen würde, aber nich so schön wäre, ist folgendes: Das Callback wird normal im MainThread ausgeführt, aber der Thread der die Resource angefordert, wird solange blockiert, wie der MainThread mit der Ausführung des Callbacks beschäftigt ist.
Ich weiß dass das Ganze ziemlich untypisch für Delphi ist, aber ich finde das Ganze in jedem Callback auf die normale Art und Weise zu synchronisieren wird sehr aufwendig, deshalb such ich nach Alternativen. Wäre super wenn mir da jmd nen kleinen Denkanstoß geben könnte :)

MfG & Thx Bergmann.

_________________
Ich weiß nicht viel, lern aber dafür umso schneller^^
glotzer
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 393
Erhaltene Danke: 49

Win 7
Lazarus
BeitragVerfasst: Di 05.02.13 21:52 
Hallo,

ich hätte da eine Idee: ich vermute mal, dass jeder der Threads eine Art Nachrichtenschleife hat. Du könntest also eine Nachricht an den Thread schicken mit dem Callback und den Parametern als Werten und dann in der Nachrichtenschleife den Callback ausführen.

Grüße
Glotzer

_________________
ja, ich schreibe grundsätzlich alles klein und meine rechtschreibfehler sind absicht
Bergmann89 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 1742
Erhaltene Danke: 72

Win7 x64, Ubuntu 11.10
Delphi 7 Personal, Lazarus/FPC 2.2.4, C, C++, C# (Visual Studio 2010), PHP, Java (Netbeans, Eclipse)
BeitragVerfasst: Di 05.02.13 22:04 
Hey,

die Idee hatte ich auch schon. Da der Thread aber sehr komplex ist und nicht jedes Objekt weiß, das es in einem Thread läuft bräuchte ich ne globale Liste, in der die Threads verwaltet werden. In der kann der Loader dann nachsehen welcher Thread den Aufruf schickt und kann dann entsprechend eine Message abschicken. Das is ne Lösung, aber so richtig zufrieden bin ich damit noch nicht :/ Trotzdem danke für's Mitdenken :) Vlt fällt uns ja noch was besseres ein...

MfG Bergmann.

_________________
Ich weiß nicht viel, lern aber dafür umso schneller^^
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19315
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mi 06.02.13 00:06 
Dafür gibt es in neueren Delphiversionen z.B. TThread.Queue:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
TThread.Queue(procedure
  begin
    ShowMessage('Mainthread: ' + IntToStr(GetCurrentThreadID));
  end);
Bei Delphi 7 musst du dir das selbst basteln. Ich würde mir dafür eine Ausführungsklasse basteln, von der dann spezielle Klassen für verschiedene Aufgaben abgeleitet sind. Dann packst du diese Objekte alle in eine globale Queue und arbeitest die aus dem Hauptthread heraus ab, z.B. in OnIdle (insbesondere bei Spielen ;-)).
Martok
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: So 10.02.13 05:41 
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Dafür gibt es in neueren Delphiversionen z.B. TThread.Queue
Das tut aber was anderes... was wir wollen, ist ja eigentlich eine Funktion in einem anderen Thread auszuführen - nicht zwingend im MainThread (bzw. praktisch nie im MainThread). Queue ist ja mehr Synchronize-ohne-Warten.

Anderes Anwendungsbeispiel:
Ein Thread macht alle Berechnungen, z.B. einer pro Welt-Teil ("Instance"). Ein weiterer Thread kümmert sich um Netzwerksockets. Wenn jetzt ein Paket kommt, ist das ja im Kontext des Netzwerkthreads. Wie kriege ich jetzt die Verarbeitung in den Kontext des Berechnungsthreads? Aktuelle beste Idee: alle Callbacks in eine Liste schreiben, und in jedem potenziell "ein-call-baren" Thread immer mal diese Liste pollen und Ereignisse abarbeiten. Ungelöste Probleme: wie macht man das waitable, und: meh, jeder Thread muss eine Loop werden.

_________________
"The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."


Zuletzt bearbeitet von Martok am So 10.02.13 18:33, insgesamt 1-mal bearbeitet
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19315
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: So 10.02.13 10:09 
Nein, das tut nix anderes, du kannst den Zielthread auch als Parameter angeben. Der Mainthread wird nur benutzt, wenn du keinen angibst. ;-)
Martok
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: So 10.02.13 18:34 
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Nein, das tut nix anderes, du kannst den Zielthread auch als Parameter angeben. Der Mainthread wird nur benutzt, wenn du keinen angibst. ;-)
Hm. Dann ist die Doku falsch, da steht nämlich, das wäre der QuellThread:
Zitat:
Queue causes the call specified by AMethod to be executed using the main thread, thereby avoiding multi-thread conflicts. The current thread is passed in the AThread parameter.

EDIT: laut Sourcecode ist es genauso wie dokumentiert. Das ist Synchronize, nur ohne Wait.

_________________
"The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19315
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: So 10.02.13 18:45 
Hmm, vielleicht habe ich das auch verwechselt. :gruebel:
Vielleicht habe ich dafür auch einen Class Helper drin... Muss ich selbst nochmal schauen... :oops:
Martok
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: Mo 11.02.13 03:01 
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Vielleicht habe ich dafür auch einen Class Helper drin... Muss ich selbst nochmal schauen... :oops:
Wär schön, wenn du mal Zeit hättest :zustimm:

Ist ja an sich nicht weiter kompliziert: ein paar InterlockedExchange auf eine verkettete Liste pro Thread. Nur muss der Thread die halt pollen, was im MainThread CheckSynchronize() tut (TApplication.Idle()). Was dann wieder mit dem Warten-auf-Events verheiratet werden muss, was die Threads eigentlich normalerweise tun. Und das wird alles irgendwie leicht "unelegant".

_________________
"The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
Bergmann89 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 1742
Erhaltene Danke: 72

Win7 x64, Ubuntu 11.10
Delphi 7 Personal, Lazarus/FPC 2.2.4, C, C++, C# (Visual Studio 2010), PHP, Java (Netbeans, Eclipse)
BeitragVerfasst: Di 12.02.13 21:51 
Heyho,

ich hab das Ganze jetzt mal umgesetzt. Erst hatte ich CriticalSections zum Synchronisieren drin, hab mich dann aber nochma mit Martok unterhalten und dann haben wir uns für SpinLocks entschieden, da das Projekt sehr zeitkritisch ist und wir so den ContextSwitch bei der CriticalSection umgehen können. Hier mal der entstandene Code:
ausblenden volle Höhe 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:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248:
249:
250:
251:
252:
253:
254:
255:
256:
unit uutlMessageThread;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, syncobjs;

type
  TutlMessage = class(TObject)
    ID: Cardinal;
    wParam: PtrInt;
    lParam: PtrInt;
    constructor Create(const aID: Cardinal; const aWParam, aLParam: PtrInt);
  end;

  TutlEventMessage = class(TutlMessage)
    Event: TEvent;
    constructor Create(const aID: Cardinal; const aWParam, aLParam: PtrInt);
    destructor Destroy; override;
  end;

  TutlMessageThread = class(TThread)
  private type
    TSingleLinkedListItem = class
      msg: TutlMessage;
      next: TSingleLinkedListItem;
    end;
  private
    fLocked: Cardinal;
    fFirst: TSingleLinkedListItem;
    fLast: TSingleLinkedListItem;
    procedure PushMsg(aMessage: TutlMessage);
    function PullMsg: TutlMessage;
    procedure ClearMessages;
    procedure LockMessages;
    procedure UnlockMessages;
  protected
    procedure ProgressMessages;
    procedure ProgressMessage(const aMessage: TutlMessage);
  public
    procedure PostMessage(const aID: Cardinal; const aWParam, aLParam: PtrInt);
    function SendMessage(const aID: Cardinal; const aWParam, aLParam: PtrInt; const aWaitTime: Cardinal = INFINITE): TWaitResult;

    constructor Create(CreateSuspended: Boolean; const StackSize: SizeUInt=DefaultStackSize);
    destructor Destroy; override;
  end;

  function utlPostMessage(const aThreadID: TThreadID; const aID: Cardinal; const aWParam, aLParam: PtrInt): Boolean;
  function utlSendMessage(const aThreadID: TThreadID; const aID: Cardinal; const aWParam, aLParam: PtrInt; const aWaitTime: Cardinal = INFINITE): TWaitResult;

implementation

uses
  uutlLogger, uutlCommonClasses;

type
  TutlMessageThreadListBase = specialize TutlKeyObjectList<TutlMessageThread, TThreadID>;
  TutlMessageThreadList = class(TutlMessageThreadListBase)
  protected
    function CompareKeys(const aKey1, aKey2: TThreadID): Integer; override;
  end;

var
  Threads: TutlMessageThreadList;

function utlPostMessage(const aThreadID: TThreadID; const aID: Cardinal;
  const aWParam, aLParam: PtrInt): Boolean;
var
  t: TutlMessageThread;
begin
  t := Threads[aThreadID];
  result := Assigned(t);
  if (result) then
    t.PostMessage(aID, aWParam, aLParam);
end;

function utlSendMessage(const aThreadID: TThreadID; const aID: Cardinal;
  const aWParam, aLParam: PtrInt; const aWaitTime: Cardinal): TWaitResult;
var
  t: TutlMessageThread;
begin
  t := Threads[aThreadID];
  if Assigned(t) then
    result := t.SendMessage(aID, aWParam, aLParam, aWaitTime)
  else
    result := wrError;
end;

function TutlMessageThreadList.CompareKeys(const aKey1, aKey2: TThreadID): Integer;
begin
  result := aKey1 - aKey2;
end;

constructor TutlMessage.Create(const aID: Cardinal; const aWParam, aLParam: PtrInt);
begin
  inherited Create;
  ID     := aID;
  wParam := aWParam;
  lParam := aLParam;
end;

constructor TutlEventMessage.Create(const aID: Cardinal; const aWParam, aLParam: PtrInt);
begin
  inherited Create(aID, aWParam, aLParam);
  Event  := TEvent.Create(nil, true, false, '');
end;

destructor TutlEventMessage.Destroy;
begin
  Event.SetEvent;
  FreeAndNil(Event);
  inherited Destroy;
end;

procedure TutlMessageThread.PushMsg(aMessage: TutlMessage);
begin
  LockMessages;
  try
    if not Assigned(fLast) then
      exit;
    fLast.next := TSingleLinkedListItem.Create;
    fLast.next.msg := aMessage;
    fLast := fLast.next;
  finally
    UnlockMessages;
  end;
end;

function TutlMessageThread.PullMsg: TutlMessage;
var
  old: TSingleLinkedListItem;
begin
  result := nil;
  LockMessages;
  try
    if not Assigned(fFirst) then
      exit;
    if Assigned(fFirst.next) then begin
      old := fFirst;
      fFirst := old.next;
      result := fFirst.msg;
      old.Free;
    end;
  finally
    UnlockMessages;
  end;
end;

procedure TutlMessageThread.ClearMessages;
var
  m: TutlMessage;
begin
  repeat
    m := PullMsg;
    if Assigned(m) then
      m.Free;
  until not Assigned(m);
end;

procedure TutlMessageThread.LockMessages;
var
  lock: Cardinal;
begin
  //SpinLock
  repeat
    lock := InterLockedExchange(fLocked, 1);
  until (lock = 0);
end;

procedure TutlMessageThread.UnlockMessages;
begin
  InterLockedExchange(fLocked, 0);
end;

procedure TutlMessageThread.ProgressMessages;
var
  i: Integer;
  m: TutlMessage;
begin
  repeat
    try
      m := PullMsg;
      if Assigned(m) then begin
        ProgressMessage(m);
        m.Free;
      end;
    except
      on e: Exception do begin
        utlLogger.Error(self, 'error while progressing message %s(ID: %d; wParam: %s; lParam: %s): %s - %s', [
          m.ClassName,
          m.ID,
          IntToHex(m.wParam, SizeOf(m.wParam) div 4),
          IntToHex(m.wParam, SizeOf(m.wParam) div 4),
          e.ClassName,
          e.Message]);
      end;
    end;
  until not Assigned(m);
end;

procedure TutlMessageThread.ProgressMessage(const aMessage: TutlMessage);
begin
//DUMMY
end;

procedure TutlMessageThread.PostMessage(const aID: Cardinal; const aWParam, aLParam: PtrInt);
var
  m: TutlMessage;
begin
  m := TutlMessage.Create(aID, aWParam, aLParam);
  PushMsg(m);
end;

function TutlMessageThread.SendMessage(const aID: Cardinal; const aWParam, aLParam: PtrInt;
  const aWaitTime: Cardinal): TWaitResult;
var
  m: TutlEventMessage;
begin
  m := TutlEventMessage.Create(aID, aWParam, aLParam);
  PushMsg(m);
  result := m.Event.WaitFor(aWaitTime);
end;

constructor TutlMessageThread.Create(CreateSuspended: Boolean; const StackSize: SizeUInt);
begin
  inherited Create(CreateSuspended, StackSize);
  fFirst     := TSingleLinkedListItem.Create;
  fLast      := fFirst;
  Threads.Add(ThreadID, self);
end;

destructor TutlMessageThread.Destroy;
begin
  Threads.Delete(ThreadID);
  LockMessages;
  try
    ClearMessages;
    FreeAndNil(fFirst);
    fLast := nil;
  finally
    UnlockMessages;
  end;
  inherited Destroy;
end;

initialization
  Threads := TutlMessageThreadList.Create(false);

finalization
  while Threads.Count > 0 do
    Threads.ItemsByID(Threads.Count-1).Free;
  FreeAndNil(Threads);

end.


Sieht da vlt jmd Probleme, hat Verbesserungsvorschläge oder Anmerkungen? Wir sind für jeden Tipp dankbar :)

MfG Bergmann

_________________
Ich weiß nicht viel, lern aber dafür umso schneller^^