2013-10-31 14 views
10

Ho creato questa proprietà generica "threadsafe", che posso utilizzare tra il thread principale e un thread in background. L'ho fatto perché ero stanco di creare Lock-Objects per tutte le mie proprietà e variabili.Proprietà threadsafe generica

TLockedProp<MyType> = class 
private 
    FMyProp:MyType; 
    PropLock:TObject; 
    procedure SetMyProp(const Value: MyType); 
    function GetMyProp: MyType; 
published 
    property Value:MyType read GetMyProp write SetMyProp; 
public 
    Constructor Create; 
    Destructor Destroy;override; 
end; 

{ TLockedProp<MyType> } 

constructor TLockedProp<MyType>.Create; 
begin 
    inherited; 
    PropLock:=TObject.create 
end; 

destructor TLockedProp<MyType>.Destroy; 
begin 
    PropLock.Free; 
    inherited; 
end; 

function TLockedProp<MyType>.GetMyProp: MyType; 
begin 
    TMonitor.Enter(PropLock); 
    result := FMyProp; 
    TMonitor.Exit(PropLock); 
end; 

procedure TLockedProp<MyType>.SetMyProp(const Value: MyType); 
begin 
    TMonitor.Enter(PropLock); 
    FMyProp := Value; 
    TMonitor.Exit(PropLock); 
end; 

Ci sono problemi che sto trascurando? Questo è un codice che utilizza questa classe di proprietà. Dimmi cosa ne pensi.

TBgThread=class(TThread) 
    private  
    FPaused: TLockedProp<boolean>; 
    FCount:TLockedProp<integer>; 

    procedure ChangeCount(pPlusMin:integer); 
    function GetPaused:boolean; 
    function GetCount:integer; 
    public 
    constructor Create; 
    destructor Destroy;override; 
    {Toggle Pause} 
    procedure PausePlay; 
    protected 
    procedure Execute;override; 
    published 
    Property Paused:boolean read GetPaused; 
    Property Count:integer read GetCount; 
    end; 
constructor TBgThread.Create(); 
begin 
    inherited Create(true);; 
    FPaused:=TLockedProp<boolean>.create; 
    FPaused.Value:=false;  
    FCount:=TLockedProp<integer>.create; 
    FCount.Value:=0; 
end; 
destructor TBgThread.Destroy; 
begin 
    FPaused.Free; 
    FCount.free;  
    inherited; 
end; 
procedure TBgThread.Execute; 
begin 
    inherited; 
    Repeat 
    if not Paused then begin 
     Try 
      //do something 
     finally 
      ChangeCount(+1); 
     end; 
    end else 
     Sleep(90); 
    Until Terminated; 
end; 

function TBgThread.GetCount: integer; 
begin 
    Result:=FCount.Value; 
end; 

procedure TBgThread.ChangeCount(pPlusMin: integer); 
begin 
    FCount.Value:=FCount.Value+pPlusMin; 
end; 

function TBgThread.GetPaused: boolean; 
begin 
    result := FPaused.Value; 
end; 

procedure TBgThread.PausePlay; 
begin 
    FPaused.Value:=not FPaused.Value; 
end; 

risposta

15

Il codice è corretto e serializzerà l'accesso in lettura/scrittura alla proprietà. L'unico commento che vorrei fare è che non è necessario creare un oggetto di blocco separato. È possibile rimuovere PropLock e bloccarlo su Self.

Ho una classe quasi identica nella mia base di codice. Le uniche differenze sono:

  1. Io uso una sezione critica piuttosto che TMonitor perché ancora non mi fido TMonitor. Le prime versioni avevano un certo numero di bug e questo ha intaccato la mia fiducia. Tuttavia, ho il sospetto che il codice TMonitor sia molto probabilmente corretto ora. Quindi non vedo ragioni per cui tu possa cambiare.
  2. Io uso try/finally con il codice che si blocca e sblocca. Questo è forse un po 'pessimista da parte mia, perché è difficile vedere come si possa recuperare utilmente da eccezioni nei metodi getter e setter. Forza dell'abitudine suppongo.

FWIW, la mia versione della classe si presenta così:

type 
    TThreadsafe<T> = class 
    private 
    FLock: TCriticalSection; 
    FValue: T; 
    function GetValue: T; 
    procedure SetValue(const NewValue: T); 
    public 
    constructor Create; 
    destructor Destroy; override; 
    property Value: T read GetValue write SetValue; 
    end; 

{ TThreadsafe<T> } 

constructor TThreadsafe<T>.Create; 
begin 
    inherited; 
    FLock := TCriticalSection.Create; 
end; 

destructor TThreadsafe<T>.Destroy; 
begin 
    FLock.Free; 
    inherited; 
end; 

function TThreadsafe<T>.GetValue: T; 
begin 
    FLock.Acquire; 
    Try 
    Result := FValue; 
    Finally 
    FLock.Release; 
    End; 
end; 

procedure TThreadsafe<T>.SetValue(const NewValue: T); 
begin 
    FLock.Acquire; 
    Try 
    FValue := NewValue; 
    Finally 
    FLock.Release; 
    End; 
end; 

Penso che non c'è davvero solo un modo di scrivere questa classe!

+1

Thnx per la tua risposta, e apprezzo molto il fatto che tu abbia condiviso la tua versione della lezione. Mi fa sentire più fiducioso come programmatore di essere riuscito a trovare questa soluzione da solo. solo 1 anno di delphi sotto la mia cintura;). –

+0

Si noti che una sezione critica può avere problemi di prestazioni, se la classe che contiene il proprio buffer è più piccola della linea della cache della CPU. Vedere https://www.delphitools.info/2011/11/30/fixing-tcriticalsection/ Potrebbe essere meglio/più sicuro aggiungere un piccolo buffer di allineamento alla definizione della classe e fare affidamento sulla sezione critica del sistema operativo, invece della classe TCriticalSection . –