2009-06-03 6 views
8

Tout le monde sait que c'est pas thread-safe:Est le C# '??' thread de l'opérateur en toute sécurité?

public StringBuilder Builder 
{ 
    get 
    { 
     if (_builder != null) 
      _builder = new StringBuilder(); 
     return _builder; 
    } 
} 

Qu'en est-ce?

public StringBuilder Builder 
{ 
    get { return _builder ?? (_builder = new StringBuilder()); } 
} 
+13

La spécification C# appelle soigneusement quelles opérations sont atomiques; Les opérateurs de coalescence nuls ne sont pas atomiques. L'opérateur de coalescence null est juste un sucre syntaxique pour votre premier morceau de code. Mais vous avez des problèmes plus importants ici; qui se soucie si le champ est threadsafe? Le constructeur n'est pas threadsafe! –

+4

Pour des questions futures dans ce sens, il serait utile que vous fournissiez une définition soigneusement formulée de ce que signifie exactement "thread safe". La sécurité du fil n'est pas un absolu; au contraire, le code est thread-safe si le contrat d'utilisation implémenté par les appelants est compatible avec celui attendu par l'appelé. Sans savoir quel contrat vous attendez, il est impossible de dire si le code le suit ou non. –

Répondre

10

BEGIN EDIT

Basé sur le titre édité, l'opérateur nul-coalescent lui-même semble être thread-safe (voir Phil Haack's analysis). Il semble, cependant, qu'il ne garantit pas contre les appels multiples potentiels au constructeur StringBuilder.

FIN EDIT

Vous avez un problème plus important avec filetage, et qui est que la propriété Builder lui-même représente l'état qui peut être partagé entre les threads. Même si vous sécurisez le thread d'initialisation paresseux, il n'y a aucune garantie que les méthodes consommant Builder le font d'une manière sûre pour les threads.

// below code makes the getter thread safe 
private object builderConstructionSynch = new object(); 
public StringBuilder Builder 
{ 
    get 
    { 
     lock (builderConstructionSynch) 
     { 
      if (_builder == null) _builder = new StringBuilder(); 
     } 
     return _builder; 
    } 
} 

La volonté ci-dessus éviter le problème de thread dans l'initialisation paresseuse de _builder, mais à moins que vous synchronisez vos appels à des méthodes d'instance de StringBuilder, vous n'êtes pas garanti la sécurité de fil dans toutes les méthodes qui consomment la propriété Builder. C'est parce que les méthodes d'instance dans StringBuilder n'ont pas été conçues pour être thread-safe. Voir le texte ci-dessous du MSDN StringBuilder page.

statiques publics (en Visual Basic) de ce type sont thread sûr. Tous les membres d'instance ne sont pas garanti être thread-safe.

Si vous consommez StringBuilder dans plusieurs threads, il est probablement préférable de l'encapsuler dans votre classe. Rendre privé et d'exposer Builder ce que le comportement dont vous avez besoin en tant que méthode publique:

public void AppendString(string toAppend) 
{ 
    lock (Builder) 
    { 
     Builder.Append(toAppend); 
    } 
} 

De cette façon, vous n'êtes pas à écrire du code de synchronisation dans tous les sens.

+0

Je pensais juste que ?? est une opération atomique. ? n'est pas thread sûr aussi? –

+3

Je ne peux pas dire si l'opérateur null-coalescing est atomique, mais mon affirmation est que vous avez un plus gros problème car StringBuilder n'est pas intrinsèquement thread-safe. –

+1

Voir la réponse éditée pour la réponse à propos de la sécurité des threads de l'opérateur de coalescence nulle (crédit à Phil Haack). Il est sûr pour les threads dans la mesure où il ne crée pas de conditions de course, mais vous pourriez potentiellement vous retrouver avec deux instances distinctes de Builder si les conditions sont parfaites. –

8

NO pour les deux versions

10

C'est plus ou moins thread-safe; vous pouvez toujours avoir deux threads faire la vérification nulle en même temps, donc créer des objets séparés et ne pas voir l'autre.

+0

Comment jugez-vous l'utilisation de Interlocked.CompareExchange (ref _builder, new StringBuilder(), null)? – LBushkin

2

Les réponses données sont correctes, les deux ne sont pas ThreadSafe. En fait, ils sont pour la plupart équivalents, et l'opérateur ?? est juste un compilateur magique pour rendre le code plus léger. Vous devez utiliser un mécanisme de synchronisation si vous voulez que cela devienne threadsafe.

2

Je n'ai pas testé moi-même approche, mais si vous voulez la sécurité des threads sans la surcharge d'un système de verrouillage et vous n'êtes pas inquiet de ce qui pourrait créer et à rejeter une instance d'objet, vous pouvez essayer ceci:

using System.Threading; 

public StringBuilder Builder 
{ 
    get 
    { 
     if (_builder != null) 
      Interlocked.CompareExchange(ref _builder, new StringBuilder(), null); 
     return _builder; 
    } 
} 

L'appel à CompareExchange() effectuera un remplacement atomique de la valeur dans _builder avec une nouvelle instance de StringBuilder uniquement si _builder == null.Toutes les méthodes de la classe Interlocked sont garanties de ne pas être préemptées par les commutateurs de threads.

+0

BTW, c'est probablement une mauvaise idée de partager une instance d'un StringBuilder à travers les threads. SB n'est intrinsèquement pas thread-safe, et il n'est pas clair, même si c'était le cas, que vous pourriez faire quelque chose de significatif à travers les threads qui ne sont pas synchronisés. – LBushkin

Questions connexes