2012-12-26 9 views
6

Mi piace il modo rapido di creare classi in C# in cui possiamo facilmente creare membri senza dover implementare il codice get e set ma (per quanto ne so) non sono thread-safe!Un modo per rendere sicuro questo thread di classe C# senza riscrivere?

public class SocksEntry 
{ 
    public int Int1 { get; set; } 
    public int Int2 { get; set; } 
} 

C# fornisce un modo rapido e semplice per aggiungere sicurezza di thread senza doverlo fare?

public class SocksEntry 
{ 
    protected object _lock = new object(); 

    // internal 
    private int _int1 = 0; 
    private int _int2 = 0; 

    // accessors 
    protected int Int1 
    { 
     get { lock (_lock) return _int1; } 
     set { lock (_lock) _int1 = value; } 
    } 

    protected int Int2 
    { 
     get { lock (_lock) return _int2; } 
     set { lock (_lock) _int2 = value; } 
    } 
} 

Ovviamente rende l'intera classe molto più grande e un dolore da creare rispetto alla versione non thread-safe!

+0

Potrebbe voler dare un'occhiata a questo: http://www.codeproject.com/Articles/33559/Handy-wrapper-class-for-thread-safe-property-acces –

+1

Il tuo codice con blocchi incorporati nella proprietà anche gli accessor non sono veramente sicuri per i thread. L'unica cosa che potrebbero comprarti sono le barriere della memoria se i metodi sono in linea, ma le operazioni su cui stai "bloccando" sono in realtà già operazioni atomiche. Avrebbe senso solo bloccare le operazioni di livello superiore che comportano il coordinamento di più stati. –

risposta

7

Fare un thread di classe sicuro è molto più difficile di quanto si possa immaginare. Potrebbe non essere abbastanza per mettere le serrature attorno ai getter e ai setter. Quindi la migliore pratica è quella di avere la classe non-thread sicura e lasciare al consumatore di questa classe la responsabilità di sincronizzarne l'accesso se ha bisogno della sicurezza del thread. Se il consumatore non ha bisogno della sicurezza del thread, ottimo, non penalizzerà le prestazioni dell'applicazione solo perché hai integrato la sicurezza dei thread nella classe senza nemmeno averne bisogno. Perché pensi che il 99,99% delle classi in .NET non siano thread-safe?

+0

Puoi dimostrarlo o no? – IamStalker

+2

Certo, posso. Tutto quello che devi fare è liberarti delle serrature nell'esempio sopra descritto. Dai un'occhiata al primo snippet di codice fornito dall'OP. Ecco come dovrebbe apparire la classe nella maggior parte dei casi. Per quanto riguarda il consumo di questa classe in modo thread-safe, beh, questo dipenderà interamente dal contesto. L'OP non ha realmente specificato come viene utilizzata questa classe, quindi è un po 'difficile fornire esempi concreti senza questo. –

+0

Ohh, tu mi hai capito, voglio vedere un thread sicuro, soluzione. – IamStalker

1

In una parola, no.

Le proprietà di implementazione automatica sono eccezionali, ma quando si raggiungono i limiti, il gioco è fatto.

Tuttavia, è possibile creare uno Visual Studio snippet (come prop) che scriverebbe per voi il codice della piastra della caldaia.

0

Credo che il problema della sicurezza filo a cui ti riferisci è qualcosa di simile:

public int Value { get; set; } 

public void Foo() 
{ 
    if (this.Value > 0) // Read the property and make a decision based on its present value 
    { 
     // Do something with the property, assuming its value has passed your test. 
     // Note that the property's value may have been changed by another thread between the previous line and this one. 
     Console.WriteLine(this.Value); 
    } 
} 

Il problema è che il valore della proprietà potrebbe essere cambiato tra quando si esaminò e quando lo si utilizza. Ciò accade se si utilizzano proprietà automatiche, variabili di supporto o blocchi presenti nella domanda. La soluzione corretta dipende da come è necessario che l'applicazione si comporti (cosa dovrebbe fare se il valore è cambiato tra una linea e l'altra). Una soluzione comune è quella di memorizzare nella cache il giusto valore nella funzione in cui lo si usa:

public int Value { get; set; } 

public void Foo() 
{ 
    int cachedValue = this.Value; 

    if (cachedValue > 0) 
    { 
     Console.WriteLine(cachedValue); 
    } 
} 

tuttavia, che non sarà necessariamente essere corretto per quello che si suppone la tua app per fare.

0

Più facile e quindi l'unico approccio corretto è quello di creare una classe immutable, quindi sono disponibili solo le operazioni di lettura.

public sealed class SocksEntry 
{ 
    public int Int1 { get; private set; } 
    public int Int2 { get; private set; } 
} 

Ma se questo non è possibile per una particolare entità di business - esporre alcuni metodi per attività commerciali, piuttosto che setter di proprietà, quindi è molto più facile per la sincronizzazione intero metodo (si pensi di transazione), piuttosto che ogni setter di proprietà che a volte ha un senso set alltogether

// lock entire business transaction if possible 
public void UpdateEntity(int a, int b) 
{ 
    lock (entityLock) 
    { 
     Int1 = a; 
     Int2 = b; 
    } 
}  
+1

Rendere una classe immutabile non lo rende thread-safe nel caso generale. Nel caso particolare con 2 proprietà integer probabilmente lo fa, ma immagina una proprietà di tipo 'List ' per esempio. Ora, anche se lo hai fatto in sola lettura, non c'è nulla che impedisca al consumatore di questa classe di chiamare il getter sulla lista e poi da un thread aggiungendo elementi alla lista e da un altro thread che itera sull'elenco allo stesso tempo. Come sapete, 'Lista ' non è un tipo sicuro per i thread e ci si mette nei guai se non si sincronizza l'accesso a questo elenco dall'esterno. –

+0

Penso che il tuo esempio con 'Lista ' non sia un esempio di classe immutabile dato che hai detto che stai ancora aggiornando l'elenco – sll

+0

Sì, ma questo è vero per qualsiasi tipo di riferimento. Una volta ottenuto il riferimento al tipo, è possibile modificarlo. Quindi il tuo esempio con tipi immutabili si applica solo ai tipi di valore. Come gestisci i tipi di riferimento in questo caso? –

0

nel modello di memoria .NET, legge e scrive dei tipi interi nativi inferiori o uguali alle dimensioni del puntatore della vostra piattaforma (cioè int a 32 bit, long a 64 bit) sono atomiche . Atomic significa che puoi leggerlo e scriverlo senza usare i lock, e non potrai mai leggere o scrivere un valore incompleto (cioè a metà strada tra una scrittura).

Tuttavia,.NET non garantisce che le scritture siano immediatamente visibili ad altri thread (ovvero è possibile scrivere nel thread A, quindi leggere nel thread B e leggere ancora un vecchio valore). Su x86 si ottiene visibilità immediata con l'architettura, ma su altre piattaforme è necessario utilizzare un campo volatile o Thread.MemoryBarrier dopo la scrittura. lock inserisce una barriera di memoria per te, quindi non devi preoccuparti di questo.

Un'altra cosa a cui pensare è come si usa la classe . Mentre un singolo accesso è atomico, un lock sarà ancora necessario se si desidera leggere o scrivere più campi come un'operazione atomica. (beh, non sempre tecnicamente - ci sono alcune ottimizzazioni che puoi fare a volte, ma tieni fede alle cose semplici mentre stai imparando)

Quindi, in breve: lo lock è piuttosto eccessivo - puoi sbarazzarti di e usa volatili/barriere se hai bisogno di visibilità immediata.

Problemi correlati