J'ai une classe statique C # accessible à partir de plusieurs threads. Deux questions:
Utilisation de la classe statique à partir de différents threads:
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 100; i++)
{
Task.Run(() =>
{
string name = MyStaticClass.GetValue(9555);
//...
});
}
}
}
Option 1 de la classe statique:
public static class MyStaticClass
{
private static MyClass _myClass = new MyClass();
public static string GetValue(int key)
{
return _myClass.GetValue(key);
}
}
Option 2 de la classe statique:
public static class MyStaticClass
{
private static MyClass _myClass;
private static object _lockObj = new object();
static MyStaticClass()
{
InitMyClass();
}
private static void InitMyClass()
{
if (_myClass == null)
{
lock(_lockObj)
{
if (_myClass == null)
{
_myClass = new MyClass();
}
}
}
}
public static string GetValue(int key)
{
return _myClass.GetValue(key);
}
}
Classe d'instance créée à partir de la classe statique:
public class MyClass
{
private Dictionary<int, Guid> _valuesDict = new Dictionary<int, Guid>();
public MyClass()
{
for (int i = 0; i < 10000; i++)
{
_valuesDict.Add(i, Guid.NewGuid());
}
}
public string GetValue(int key)
{
if (_valuesDict.TryGetValue(key, out Guid value))
{
return value.ToString();
}
return string.Empty;
}
}
Devrais-je verrouiller lors de l'initialisation de champs statiques privés à partir d'un constructeur statique?
N'enterrons pas la led ici:
Ne jamais verrouiller un constructeur statique. Les constructeurs statiques sont déjà verrouillés par le framework, de sorte qu'ils ne s'exécutent qu'une seule fois sur un thread.
Ceci est un cas particulier de bon conseil plus général: ne faites jamais rien de fantaisie avec des threads dans un constructeur statique. Le fait que les constructeurs statiques soient effectivement verrouillés et que ce verrou puisse être contesté par tout code accédant à votre type signifie que vous pouvez très rapidement vous retrouver dans des blocages inattendus et difficiles à voir. Je donne un exemple ici: https://ericlippert.com/2013/01/31/the-no-lock-deadlock/
Si vous souhaitez une initialisation lente, utilisez la construction Lazy<T>
; il a été écrit par des experts qui savent comment le mettre en sécurité.
Mes champs de champs statiques privés sont-ils en sécurité lorsque le champ est initialisé lors de la déclaration?
La sécurité des threads consiste à préserver les invariants de programme lorsque des éléments de programme sont appelés à partir de plusieurs threads. Vous n'avez pas dit quels sont vos invariants, il est donc impossible de dire si votre programme est "sûr".
Si l'invariant qui vous préoccupe est que l'exécution du constructeur statique est exécutée avant l'exécution de la première méthode statique ou la création de la première instance d'un type, C # vous le garantit. Bien sûr, si vous écrivez du code fou dans votre constructeur statique, des choses folles peuvent se produire, essayez donc de garder vos constructeurs statiques très simples.
les champs de la classe statique ne sont pas thread-safe par défaut et doivent être évités sauf si c'est uniquement à des fins de lecture . Ici, le côté négatif est également "verrouillé", il créera un traitement sérialisé dans un environnement multi-thread.
public static class MyStaticClass
{
private static MyClass _myClass;
private static object _lockObj;
static MyStaticClass()
{
_myClass = new MyClass();
_lockObj = new object();
}
public static string GetValue(int key)
{
return _myClass.GetValue(key);
}
public static void SetValue(int key)
{
lock(_lockObj)
{
_myClass.SetValue(key);
}
}
}
Le constructeur statique d'une classe s'exécute au plus une fois dans un domaine d'application donné. L'exécution d'un constructeur statique est déclenchée par le premier des événements suivants à se produire dans un domaine d'application:
Une instance de la classe est créée.
Tous les membres statiques de la classe sont référencés.
Vos deux options fonctionnent donc, mais vous n'avez pas besoin de verrouiller votre constructeur comme vous le feriez dans l'option deux.
Cependant, en général: Si vous avez un accès en écriture à une variable statique en dehors du constructeur statique, chaque accès en lecture et en écriture à cette variable doit être verrouillé.
Votre deuxième version est préférable. Vous pouvez le verrouiller un peu plus en rendant votre champ readonly
:
public static class MyStaticClass
{
private static readonly MyClass _myClass = new MyClass();
public static string GetValue(int key)
{
return _myClass.GetValue(key);
}
}
Votre intention semble être que _myClass
est initialement défini sur une instance de MyClass
et jamais défini sur une autre. readonly
accomplit cela en spécifiant qu'il ne peut être défini qu'une fois, soit dans un constructeur statique, soit en l'initialisant comme ci-dessus. Non seulement un autre thread ne peut pas le définir, mais toute tentative de le modifier entraînera une erreur du compilateur.
Vous pouvez omettre readonly
et simplement ne jamais redéfinir _myClass
, mais readonly
communique et applique votre intention.
Voici où cela devient plus compliqué: Votre référence à une instance de MyClass
est thread-safe. Vous n'avez pas à vous demander si divers threads la remplaceront par une instance différente (ou la définirez sur null). Elle sera instanciée avant que les threads ne tentent d'interagir avec elle.
Ce que cela fait pas est de rendre le thread MyClass
sûr. Sans savoir ce que cela fait ou comment vous interagissez avec cela, il m'est impossible de dire quels sont les besoins ou les préoccupations.
Si cela vous pose un problème, une solution consiste à utiliser une variable lock
pour empêcher tout accès simultané qui ne devrait pas se produire, exactement comme @ Mahi1722 l'a démontré. J'inclus le code de cette réponse (pas pour plagier, mais si quelque chose arrive à cette réponse, celle-ci fera référence à une réponse qui n'existe pas.)
public static class MyStaticClass
{
private static MyClass _myClass = new MyClass();
private static object _lockObj = new object();
public static string GetValue(int key)
{
return _myClass.GetValue(key);
}
public static void SetValue(int key)
{
lock(_lockObj)
{
_myClass.SetValue(key);
}
}
}
Les deux méthodes qui interagissent avec _myClass
verrouillent à l'aide de _lockObject
, ce qui signifie que toute exécution de l'une ou de l'autre bloquera pendant qu'un autre thread est en train de l'exécuter.
C'est une approche valable. Une autre consiste à rendre le thread MyClass
sûr, soit en utilisant des collections simultanées, soit en implémentant de tels verrous au sein de cette classe. De cette façon, vous n'avez pas à utiliser les instructions lock
dans chaque classe qui utilise une instance de MyClass
. Vous pouvez simplement l'utiliser en sachant qu'il gère cela en interne.