在 Delphi/Linux 程式中,假設我有兩個正在運行的執行緒,ThreadA 和 ThreadB。在某個時間點 ThreadB 需要讓 ThreadA 執行一個函式并阻塞直到函式回傳。
在 Delphi 中,我們有 TThread.Synchronize 來完成這項作業,但前提是 ThreadA 是主執行緒。
任何想法?我使用 Delphi,但也歡迎使用 C 代碼回答。
uj5u.com熱心網友回復:
為此,執行緒必須協作,沒有跨執行緒觸發事件的機制。但是,如果您準備實作這樣的機制,這并不難,當然您可以查看源代碼以TThread.Synchronize獲取提示。
借用源代碼,TThread.Syncrhonize我想出了以下內容。您將不得不讓協作執行緒將其佇列作為其主回圈的一部分進行檢查——這當然是TThread.Synchronize作業原理。
以下代碼基于我們在生產中使用的代碼 - 如果有評論或參考不在單元中的專案,我深表歉意。沒有提供函式結果的機制,但這可以通過使用不同的呼叫模板來解決(因此結果型別是已知的)。我已經允許一個 Result TObject(即使沒有辦法知道當前應該是什么),以便在需要時可以回傳多值結果。
以下沒有特定于 Windows 的代碼,因此它應該可以按照您的要求在 Linux 上運行。
unit Unit1;
interface
uses Classes, SyncObjs, Generics.Collections;
type
TQueuedCallback = class(TObject)
protected
_pEvent: TEvent;
_pResult: TObject;
_fnMethod: TThreadMethod;
_fnProcedure: TThreadProcedure;
public
property Event: TEvent read _pEvent write _pEvent;
property Result: TObject read _pResult write _pResult;
property Method: TThreadMethod read _fnMethod write _fnMethod;
property Proc: TThreadProcedure read _fnProcedure write _fnProcedure;
end;
TQueueableThread = class(TThread)
protected
_pCSLock: TCriticalSection;
_pQueuedCalls: TList<TQueuedCallback>;
_haSignals: THandleObjectArray;
_pQueueEvent: TEvent;
_pStopEvent: TEvent;
_dwMaxWait: Cardinal;
procedure _DoWork(nEventIndex: Integer); virtual; abstract; // where th thread does it's work
procedure _ExecuteQueued(blAll: Boolean = False); virtual;
public
destructor Destroy; override;
procedure AfterConstruction(); override;
procedure Execute(); override;
procedure QueueProcedure(fnMethod: TThreadMethod); overload; virtual;
procedure QueueProcedure(fnProcedure: TThreadProcedure); overload; virtual;
procedure QueueProcedureAndWait(fnMethod: TThreadMethod); overload; virtual;
procedure QueueProcedureAndWait(fnProcedure: TThreadProcedure); overload; virtual;
function QueueProcedureAndWaitForResult(fnMethod: TThreadMethod): TObject; overload; virtual;
function QueueProcedureAndWaitForResult(fnProcedure: TThreadProcedure): TObject; overload; virtual;
end;
implementation
uses SysUtils;
{ TQueueableThread }
procedure TQueueableThread._ExecuteQueued(blAll: Boolean);
begin
repeat
Self._pCSLock.Enter();
if(Self._pQueuedCalls.Count>0) then
begin
if(Assigned(Self._pQueuedCalls.Items[0].Method)) then
Self._pQueuedCalls.Items[0].Method()
else if(Assigned(Self._pQueuedCalls.Items[0].Proc)) then
Self._pQueuedCalls.Items[0].Proc();
// No mechanism for supplying a result ...
if(Self._pQueuedCalls.Items[0]._pEvent<>nil) then
Self._pQueuedCalls.Items[0]._pEvent.SetEvent()
else
Self._pQueuedCalls.Items[0].Free;
Self._pQueuedCalls.Delete(0);
end;
blAll:=(blAll And (Self._pQueuedCalls.Count>0));
Self._pCSLock.Leave();
until not blAll;
end;
destructor TQueueableThread.Destroy;
begin
if(Self._pQueuedCalls<>nil) then
begin
while(Self._pQueuedCalls.Count>0) do
begin
if(Self._pQueuedCalls.Items[0].Event<>nil) then
Self._pQueuedCalls.Items[0].Event.SetEvent()
else
Self._pQueuedCalls.Items[0].Free();
Self._pQueuedCalls.Delete(0);
end;
FreeAndNil(Self._pQueuedCalls);
end;
end;
procedure TQueueableThread.AfterConstruction();
begin
inherited;
Self._pCSLock:=TCriticalSection.Create();
Self._pQueuedCalls:=TList<TQueuedCallback>.Create();
SetLength(Self._haSignals, 2);
Self._pQueueEvent:=TEvent.Create();
Self._haSignals[0]:=Self._pQueueEvent;
Self._pStopEvent:=TEvent.Create();
Self._haSignals[1]:=Self._pStopEvent;
Self._dwMaxWait:=30000;
end;
procedure TQueueableThread.Execute();
var
dwWaitResult: TWaitResult;
nEventIndex: Integer;
nLoop: Integer;
pSignalled: THandleObject;
begin
while(not Self.Terminated) do
begin
//LogThreadMessage(GetCurrentThreadId(), Self.ClassType, Format('WaitingFor: %u', [Self._MaxWaitTime]));
dwWaitResult:=THandleObject.WaitForMultiple(Self._haSignals, Self._dwMaxWait, False, pSignalled);
//LogThreadMessage(GetCurrentThreadId(), Self.ClassType, Format('WaitForMultipleObjects Result: %u', [dwWaitResult]));
if(dwWaitResult=wrError) then
Self.Terminate;
if not Self.Terminated then
begin
if(pSignalled=Self._pQueueEvent) then
begin
Self._ExecuteQueued(True);
Self._pQueueEvent.ResetEvent();
end
else if(pSignalled=Self._pStopEvent) then
Self.Terminate()
else
begin
nEventIndex:=-2;
if(dwWaitResult=wrTimeout) then
nEventIndex:=-1
else
begin
nLoop:=0;
while( (nEventIndex<0) And (nLoop<Length(Self._haSignals)) ) do
begin
if(Self._haSignals[nLoop]=pSignalled) then
nEventIndex:=nLoop
else
Inc(nLoop);
end;
if(nEventIndex>-2) then
begin
try
Self._DoWork(nEventIndex);
except
on e: Exception do
// error handling
end;
end;
end;
end;
end;
end;
end;
procedure TQueueableThread.QueueProcedure(fnMethod: TThreadMethod);
var
pQueue: TQueuedCallback;
begin
if(Assigned(fnMethod)) then
begin
Self._pCSLock.Enter();
pQueue:=TQueuedCallback.Create();
pQueue.Method:=fnMethod;
Self._pQueuedCalls.Add(pQueue);
Self._pQueueEvent.SetEvent();
Self._pCSLock.Leave();
end;
end;
procedure TQueueableThread.QueueProcedure(fnProcedure: TThreadProcedure);
var
pQueue: TQueuedCallback;
begin
if(Assigned(fnProcedure)) then
begin
Self._pCSLock.Enter();
pQueue:=TQueuedCallback.Create();
pQueue.Proc:=fnProcedure;
Self._pQueuedCalls.Add(pQueue);
Self._pQueueEvent.SetEvent();
Self._pCSLock.Leave();
end;
end;
procedure TQueueableThread.QueueProcedureAndWait(fnMethod: TThreadMethod);
var
pQueue: TQueuedCallback;
begin
if(Assigned(fnMethod)) then
begin
Self._pCSLock.Enter();
pQueue:=TQueuedCallback.Create();
pQueue.Method:=fnMethod;
pQueue.Event:=TEvent.Create();
Self._pQueuedCalls.Add(pQueue);
Self._pQueueEvent.SetEvent();
Self._pCSLock.Leave();
pQueue._pEvent.WaitFor(INFINITE);
FreeAndNil(pQueue._pEvent);
FreeAndNil(pQueue);
end;
end;
procedure TQueueableThread.QueueProcedureAndWait(fnProcedure: TThreadProcedure);
var
pQueue: TQueuedCallback;
begin
if(Assigned(fnPRocedure)) then
begin
Self._pCSLock.Enter();
pQueue:=TQueuedCallback.Create();
pQueue.Proc:=fnProcedure;
pQueue.Event:=TEvent.Create();
Self._pQueuedCalls.Add(pQueue);
Self._pQueueEvent.SetEvent();
Self._pCSLock.Leave();
pQueue._pEvent.WaitFor(INFINITE);
FreeAndNil(pQueue._pEvent);
FreeAndNil(pQueue);
end;
end;
function TQueueableThread.QueueProcedureAndWaitForResult(fnMethod: TThreadMethod): TObject;
var
pQueue: TQueuedCallback;
begin
Result:=nil;
if(Assigned(fnMethod)) then
begin
Self._pCSLock.Enter();
pQueue:=TQueuedCallback.Create();
pQueue.Method:=fnMethod;
pQueue.Event:=TEvent.Create();
Self._pQueuedCalls.Add(pQueue);
Self._pQueueEvent.SetEvent();
Self._pCSLock.Leave();
pQueue._pEvent.WaitFor(INFINITE);
Result:=pQueue._pResult;
FreeAndNil(pQueue._pEvent);
FreeAndNil(pQueue);
end;
end;
function TQueueableThread.QueueProcedureAndWaitForResult(fnProcedure: TThreadProcedure): TObject;
var
pQueue: TQueuedCallback;
begin
Result:=nil;
if(Assigned(fnProcedure)) then
begin
Self._pCSLock.Enter();
pQueue:=TQueuedCallback.Create();
pQueue.Proc:=fnProcedure;
pQueue.Event:=TEvent.Create();
Self._pQueuedCalls.Add(pQueue);
Self._pQueueEvent.SetEvent();
Self._pCSLock.Leave();
pQueue._pEvent.WaitFor(INFINITE);
Result:=pQueue._pResult;
FreeAndNil(pQueue._pEvent);
FreeAndNil(pQueue);
end;
end;
end.
You could have inherited classes of TQueuedCallback that use a specific calling template, and this would be one way to identify the return value
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/455092.html
