2009-03-24 11 views
7

Edit per intro:
Sappiamo che un parametro ref in C# passa un di riferimento ad una variabile, che permette la variabile esterna in sé di essere cambiato all'interno di un metodo chiamato. Ma il riferimento è gestito come un puntatore C (leggendo i contenuti correnti della variabile originale con ogni accesso a quel parametro e modificando la variabile originale con ogni modifica al parametro), oppure il metodo chiamato può contare su un riferimento coerente per il durata della chiamata? Il primo porta alcuni problemi di sicurezza del thread. In particolare:I parametri di riferimento .NET sono thread-safe o vulnerabili a un accesso multithreaded non sicuro?

Ho scritto un metodo statico in C# che passa un oggetto per riferimento:

public static void Register(ref Definition newDefinition) { ... } 

Il chiamante fornisce un completato ma non ancora registrati Definition oggetto, e dopo un po 'di coerenza verifica che "registra" la definizione che hanno fornito. Tuttavia, se esiste già una definizione con la stessa chiave, non può registrare la nuova e invece il loro riferimento viene aggiornato a "ufficiale" Definition per quella chiave.

Vogliamo che questo sia rigorosamente thread-safe, ma uno scenario patologico mi viene in mente. Supponiamo che il client (usando la biblioteca) condivide il riferimento in modo non-thread-safe, ad esempio utilizzando un membro statico piuttosto che una variabile locale:

private static Definition riskyReference = null; 

Se un thread imposta riskyReference = new Definition("key 1");, riempie la definizione, e chiama il nostro Definition.Register(ref riskyReference); mentre un altro thread decide anche di impostare riskyReference = new Definition("key 2");, siamo sicuri che nel nostro metodo Register il riferimento newDefinition che stiamo gestendo non verrà modificato da noi da altri thread (perché il riferimento all'oggetto è stato copiato e sarà copiato quando torniamo?), o può quell'altro thread sostituire l'oggetto su di noi nel mezzo della nostra esecuzione (se stiamo facendo riferimento a un puntatore alla posizione di memoria originale ???) e quindi interrompere il nostro controllo mentale?

Si noti che questo è diverso dalle modifiche all'oggetto sottostante stesso, che sono naturalmente possibili per un tipo di riferimento (classe), ma può essere facilmente protetto da un blocco appropriato all'interno di tale classe. Tuttavia, non possiamo proteggere le modifiche allo spazio variabile di un client esterno stesso! Dovremmo creare la nostra copia del parametro nella parte superiore del metodo e sovrascrivere il parametro in fondo (per esempio), ma sembrerebbe avere più senso da fare per il compilatore dato la follia di gestire un riferimento non sicuro.

Quindi, tenderei a pensare che il riferimento possa essere copiato e copiato dal compilatore in modo che il metodo gestisca un riferimento coerente all'oggetto originale (finché non cambia il proprio riferimento quando lo desidera) indipendentemente da ciò che potrebbe accadere alla posizione originale su altri thread. Ma abbiamo difficoltà a trovare una risposta definitiva su questo punto nella documentazione e nella discussione dei parametri ref.

Qualcuno può alleviare la mia preoccupazione con una citazione definitiva?

Edit per conclusione: (! Grazie Marc)
Constatato con un esempio di codice multi-threaded e pensare ulteriormente, ha senso che è proprio la non-automaticamente-threadsafe comportamenti Ero preoccupato. Un punto di "ref" è passare le grandi strutture per riferimento piuttosto che copiarle.Un altro motivo è che potresti vuoi impostare un monitoraggio a lungo termine di una variabile e devi passare un riferimento ad esso che vedrà le modifiche alla variabile (ad esempio, la modifica tra null e un oggetto live), che un automatico copy-in/copy-out non consentirebbe.

Quindi, per rendere il nostro Register metodo robusto contro la follia del cliente, potremmo implementare le cose come:

public static void Register(ref Definition newDefinition) { 
    Definition theDefinition = newDefinition; // Copy in. 
    //... Sanity checks, actual work... 
    //...possibly changing theDefinition to a new Definition instance... 
    newDefinition = theDefinition; // Copy out. 
} 

avevano ancora hanno i loro problemi di threading per quanto riguarda quello che finiscono per ottenere, ma almeno la loro follia non avrebbe infranto il nostro processo di controllo del benessere mentale e probabilmente avrebbe superato uno stato brutto dopo i nostri controlli.

risposta

7

Quando si utilizza ref, si passa l'indirizzo del campo/variabile del chiamante. Quindi sì: due thread possono competere sul campo/variabile - ma solo se entrambi stanno parlando con quel campo/variabile. Se hanno un campo/variabile diverso nella stessa istanza, allora le cose sono sensate (supponendo che sia immutabile).

Ad esempio; nel codice sottostante, Registerfa vedere le modifiche apportate a Mutate alla variabile (ogni istanza di oggetto è effettivamente immutabile).

using System; 
using System.Threading; 
class Foo { 
    public string Bar { get; private set; } 
    public Foo(string bar) { Bar = bar; } 
} 
static class Program { 
    static Foo foo = new Foo("abc"); 
    static void Main() { 
     new Thread(() => { 
      Register(ref foo); 
     }).Start(); 
     for (int i = 0; i < 20; i++) { 
      Mutate(ref foo); 
      Thread.Sleep(100); 
     } 
     Console.ReadLine(); 
    } 
    static void Mutate(ref Foo obj) { 
     obj = new Foo(obj.Bar + "."); 
    } 
    static void Register(ref Foo obj) { 
     while (obj.Bar.Length < 10) { 
      Console.WriteLine(obj.Bar); 
      Thread.Sleep(100); 
     } 
    } 
} 
+1

Okay, questo esempio * fa * dimostrare che i parametri ref in C# * non * copie thread-safe, in modo che il cliente a fare qualcosa di stupido e patologica poteva cambiare quello oggetto siamo gestione nel mezzo del nostro metodo. Avremmo bisogno di fare il nostro copy-in/copy-out se vogliamo difenderci con forza. Grazie! –

6

No, non è "copia dentro, copia". Invece, la variabile stessa viene effettivamente passata. Non il valore, ma la variabile stessa. Le modifiche apportate durante il metodo sono visibili a qualsiasi altra cosa che guarda la stessa variabile.

Si può vedere questo senza alcuna threading essere coinvolti:

using System; 

public class Test 
{ 
    static string foo; 

    static void Main(string[] args) 
    { 
     foo = "First"; 
     ShowFoo(); 
     ChangeValue(ref foo); 
     ShowFoo(); 
    } 

    static void ShowFoo() 
    { 
     Console.WriteLine(foo); 
    } 

    static void ChangeValue(ref string x) 
    { 
     x = "Second"; 
     ShowFoo(); 
    } 
} 

L'uscita di questo è Primo, Secondo, In secondo luogo - la chiamata a ShowFoo()entro ChangeValue mostra che il valore di foo ha già cambiato, che è esattamente la situazione che ti preoccupa.

La soluzione

Fai Definition immutabile se non fosse prima, e cambiare la tua firma di metodo a:

public static Definition Register(Definition newDefinition) 

Poi il chiamante può sostituire la variabile, se vogliono, ma la vostra la cache non può essere inquinata da un altro thread furbo. Il chiamante potrebbe fare qualcosa di simile:

myDefinition = Register(myDefinition); 
+0

Siamo andati specificatamente al parametro ref per evitare il requisito che il chiamante sostituisca la variabile con i risultati della chiamata, perché potrebbero non riuscire a farlo e normalmente funzionerebbe correttamente (utilizza lo stesso oggetto), ma a volte potrebbe morderli. Ma hai ragione che eviterebbe questo problema. –

+0

Potrebbero usare una variabile locale invece della giusta variabile condivisa, che potrebbe mordere anche loro. Non puoi garantire che il chiamante sia sensibile, ma * puoi * garantire che il tuo codice si comporti in modo ragionevole. –

+0

Sì, ma vogliamo che la nostra API sia infallibile come possiamo farcela, non tende a farli rovinare tutto.;-) Finché usano una variabile locale per la nuova Definizione (come dovrebbero), l'uso di ref rende più facile per loro farlo bene. La mia preoccupazione è la paranoia sull'uso patologico che ci ha spezzato. –

Problemi correlati