2009-03-24 11 views
7

Modifier pour l'intro:
Nous savons qu'un paramètre ref en C# passe une référence à une variable, ce qui permet lui-même la variable externe à modifier dans une méthode appelée. Mais la référence est-elle traitée comme un pointeur C (lecture du contenu actuel de la variable d'origine avec chaque accès à ce paramètre et modification de la variable d'origine à chaque modification du paramètre) ou la méthode appelée peut-elle s'appuyer sur une référence cohérente? durée de l'appel? Le premier soulève des problèmes de sécurité des threads. En particulier:Les paramètres ref .NET sont-ils sécurisés pour les threads ou vulnérables à un accès multithread non sécurisé?

J'ai écrit une méthode statique en C# qui passe un objet par référence:

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

L'appelant fournit une terminée, mais non encore enregistré objet Definition, et après quelques vérifications, nous la cohérence "enregistrer" la définition qu'ils ont fournie. Cependant, s'il existe déjà une définition avec la même clé, elle ne peut pas enregistrer la nouvelle et à la place, sa référence est mise à jour pour la clé "officielle" Definition.

Nous voulons que ce soit rigoureusement thread-safe, mais un scénario pathologique vient à l'esprit. Supposons que le client (à l'aide de notre bibliothèque) partage la référence d'une manière non-thread-safe, comme l'utilisation d'un membre statique plutôt que d'une variable locale:

private static Definition riskyReference = null; 

Si un thread fixe riskyReference = new Definition("key 1");, remplit la définition, et appelle notre Definition.Register(ref riskyReference); alors qu'un autre thread décide également de définir riskyReference = new Definition("key 2");, est-on garanti que dans notre méthode Register la référence newDefinition que nous traitons ne sera pas modifiée sur nous par d'autres threads (parce que la référence à l'objet a été copiée et sera copié lors de notre retour?), ou cet autre thread peut-il remplacer l'objet sur nous au milieu de notre exécution (si nous référençons un pointeur vers l'emplacement de stockage d'origine ???) et ainsi casser notre vérification de la santé mentale? Notez que ceci est différent des changements à l'objet sous-jacent lui-même, qui sont bien sûr possibles pour un type de référence (classe), mais qui peuvent être facilement protégés par un verrouillage approprié dans cette classe. Cependant, nous ne pouvons pas protéger les modifications apportées à l'espace variable d'un client externe lui-même! Nous devrions faire notre propre copie du paramètre en haut de la méthode et écraser le paramètre en bas (par exemple), mais cela semblerait plus logique pour le compilateur de faire pour nous étant donné la folie de gérer un référence dangereuse. Donc, j'aurais tendance à penser que la référence peut être copiée et copiée par le compilateur afin que la méthode gère une référence cohérente à l'objet original (jusqu'à ce qu'elle change sa propre référence quand elle le souhaite) indépendamment de ce qui pourrait arriver à l'emplacement d'origine sur d'autres threads. Mais nous avons du mal à trouver une réponse définitive sur ce point dans la documentation et la discussion des paramètres ref.

Quelqu'un peut-il apaiser mon inquiétude avec une citation définitive?

Modifier pour la conclusion: (Merci Marc)
Après avoir confirmé avec un exemple de code multi-thread et penser plus loin, il est logique qu'il est en effet le comportement non automatiquement threadsafe qui J'étais inquiet pour ça. Un point de "ref" est de passer de grandes structures par référence plutôt que de les copier.Une autre raison est que vous pourriez vouloir de mettre en place un suivi à long terme d'une variable et la nécessité de passer une référence à ce qui voir des changements à la variable (par exemple. Le changement entre null et un objet en direct), qui une automatique copier/copier ne permettrait pas.

Ainsi, pour rendre notre méthode Register robuste contre la folie du client, nous pourrions mettre en œuvre comme:

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. 
} 

Ils auraient encore leurs propres problèmes de filetage pour autant que ce qu'ils finissent par obtenir, mais au moins leur démence ne briserait pas notre propre processus de vérification de la santé mentale et pourrait même laisser passer un mauvais état après nos vérifications.

Répondre

7

Lorsque vous utilisez ref, vous passez l'adresse du champ/la variable de l'appelant. Donc oui: deux threads peuvent rivaliser sur le champ/variable - mais seulement s'ils parlent tous deux à ce champ/variable. Si elles ont un champ/une variable différent de la même instance, alors les choses sont saines (en supposant qu'elles soient immuables).

Par exemple; dans le code ci-dessous, Registerne fait voir les changements que Mutate fait à la variable (chaque instance d'objet est effectivement immuable).

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

D'accord, cet exemple * fait * montre que les paramètres ref en C# * ne sont pas * copies thread-safe, de sorte que le faire client quelque chose de stupide et pathologique pourrait changer dans quel but nous sommes manipulation au milieu de notre méthode. Nous aurions besoin de faire notre propre copie/copie si nous voulons nous prémunir contre cela. Merci! –

6

Non, ce n'est pas "copier, copier". Au lieu de cela, la variable elle-même est effectivement transmise. Pas la valeur, mais la variable elle-même. Les modifications effectuées au cours de la méthode sont visibles pour toute autre personne qui regarde la même variable.

Vous pouvez le voir sans filetage être impliqué:

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(); 
    } 
} 

La sortie de c'est premier, deuxième, deuxième - l'appel à ShowFoo()au sein ChangeValue montre que la valeur de foo a déjà changé, ce qui est exactement la situation qui vous préoccupe.

La solution

Faire Definition immuable si elle était pas avant, et changer votre signature de la méthode à:

public static Definition Register(Definition newDefinition) 

Ensuite, l'appelant peut remplacer leur variable s'ils veulent, mais votre le cache ne peut pas être pollué par un autre thread sly. L'appelant ferait quelque chose comme:

myDefinition = Register(myDefinition); 
+0

Nous spécifiquement allé au paramètre ref pour éviter l'exigence que l'appelant remplacer la variable avec les résultats de l'appel, car ils pourraient manquer de le faire et il serait généralement travailler d'accord (utilise même objet), mais parfois peut les mordre. Mais vous avez raison, cela éviterait ce problème. –

+0

Ils peuvent utiliser une variable locale au lieu de la variable droit commun, qui les mordent aussi bien. Vous ne pouvez pas garantir que l'appelant sera sensible, mais vous pouvez * garantir * que votre propre code se comporte raisonnablement. –

+0

Oui, mais nous voulons que notre API soit aussi à toute épreuve que nous pouvons le faire, pas tendance à les faire foirer.;-) Tant qu'ils utilisent une variable locale pour la nouvelle définition (comme ils le devraient), l'utilisation de ref leur facilite la tâche. Mon inquiétude est la paranoïa de l'usage pathologique qui nous brise. –