it-swarm.com.de

Sind private Felder in der statischen Klasse von C # sicher?

Ich habe eine statische C # -Klasse, auf die von mehreren Threads aus zugegriffen wird. Zwei Fragen:

  1. Sind meine privaten statischen Felder ein Thread, wenn das Feld bei der Deklaration initialisiert wird?
  2. Sollte ich beim Erstellen privater statischer Felder innerhalb des statischen Konstruktors sperren?

Verwendung der statischen Klasse aus verschiedenen Threads:

class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 100; i++)
            {
                Task.Run(() =>
                {
                    string name = MyStaticClass.GetValue(9555);
                    //...
                });
            }
        }
}

Option 1 der statischen Klasse:

public static class MyStaticClass
    {
        private static MyClass _myClass = new MyClass();

        public static string GetValue(int key)
        {
            return _myClass.GetValue(key);
        }
    }

Option 2 der statischen Klasse:

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

Aus der statischen Klasse erstellte Instanzklasse:

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;
        }
    }
7
Yevgeny Granat

Sollte ich beim Initialisieren privater statischer Felder innerhalb des statischen Konstruktors sperren?

Lassen Sie uns nicht das Lede hier begraben:

Sperren Sie niemals einen statischen Konstruktor. Statische Konstruktoren sind bereits vom Framework gesperrt, sodass sie genau einmal in einem Thread ausgeführt werden.

Dies ist ein Spezialfall für einen allgemeineren guten Ratschlag: Machen Sie niemals etwas mit Threads in einem statischen Konstruktor. Die Tatsache, dass statische Konstruktoren effektiv gesperrt sind und diese Sperre durch jeden Code, der auf Ihren Typ zugreift, angefochten werden kann, bedeutet, dass Sie sehr schnell in Deadlocks geraten können, die Sie nicht erwartet haben und schwer zu erkennen sind. Ich gebe hier ein Beispiel: https://ericlippert.com/2013/01/31/the-no-lock-deadlock/

Wenn Sie eine langsame Initialisierung wünschen, verwenden Sie das Konstrukt Lazy<T>. Es wurde von Experten geschrieben, die wissen, wie sie sicher sind.

Sind meine privaten statischen Felder ein Thread, wenn das Feld bei der Deklaration initialisiert wird?

Threadsicherheit ist die Beibehaltung von Programminvarianten, wenn Programmelemente von mehreren Threads aufgerufen werden. Sie haben nicht gesagt, was Ihre Invarianten sind. Es ist also unmöglich zu sagen, ob Ihr Programm "sicher" ist.

Wenn Sie besorgt sind, dass der statische Konstruktor ausgeführt wird, bevor die erste statische Methode ausgeführt wird oder die erste Instanz eines Typs erstellt wird, gewährleistet C #, dass dies der Fall ist. Wenn Sie in Ihrem statischen Konstruktor verrückten Code schreiben, können natürlich verrückte Dinge passieren. Versuchen Sie also, Ihre statischen Konstruktoren sehr einfach zu halten.

13
Eric Lippert

felder der statischen Klasse sind standardmäßig nicht threadsicher und sollten es vermeiden, es sei denn, dies ist nur zum Lesen gedacht.

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);
        }
    }
}
1
Mahi1722

von C # in der Tiefe :

Der statische Konstruktor für eine Klasse wird höchstens einmal in einer bestimmten Anwendungsdomäne ausgeführt. Die Ausführung eines statischen Konstruktors wird durch das erste der folgenden Ereignisse ausgelöst, die in einer Anwendungsdomäne auftreten:

Eine Instanz der Klasse wird erstellt.

Alle statischen Member der Klasse werden referenziert.

Beide Optionen funktionieren also, aber Sie müssen Ihren Konstruktor nicht wie in Option zwei sperren.

Generell gilt jedoch: Wenn Sie auf eine statische Variable außerhalb des statischen Konstruktors schreibend zugreifen können, muss jeder Lese- und Schreibzugriff auf diese Variable gesperrt sein. 

0
Jeremy Benks

Ihre zweite Version ist vorzuziehen. Sie können es etwas mehr sperren, indem Sie Ihr Feld zu readonly machen:

public static class MyStaticClass
{
    private static readonly MyClass _myClass = new MyClass();

    public static string GetValue(int key)
    {
        return _myClass.GetValue(key);
    }
}

Ihre Absicht scheint zu sein, dass _myClass anfänglich auf eine Instanz von MyClass und nie auf eine andere gesetzt ist. readonly erreicht dies, indem angegeben wird, dass es nur einmal gesetzt werden kann, entweder in einem statischen Konstruktor oder durch Initialisierung wie oben. Ein anderer Thread kann ihn nicht nur nicht festlegen, sondern jeder Versuch, ihn zu ändern, führt zu einem Compiler-Fehler.

Sie könnten readonly weglassen und einfach nie wieder _myClass setzen, readonly kommuniziert und setzt Ihre Absicht durch.

Hier wird es kniffliger: Ihr reference auf eine Instanz von MyClass ist Thread-sicher. Sie müssen sich keine Gedanken darüber machen, ob verschiedene Threads sie durch eine andere Instanz ersetzen (oder auf null setzen), und es wird instanziiert, bevor Threads versuchen, mit ihr zu interagieren.

Was macht nicht macht, macht MyClass thread sicher. Ohne zu wissen, was es tut oder wie Sie damit umgehen, kann ich nicht sagen, was die Bedürfnisse oder Sorgen sind.

Wenn dies ein Problem ist, besteht ein Ansatz darin, eine lock zu verwenden, um den gleichzeitigen Zugriff zu verhindern, der nicht auftreten sollte, genau wie bei @ Mahi1722 gezeigt. Ich füge den Code aus dieser Antwort hinzu (nicht zu plagiieren), aber wenn dieser Antwort etwas zustößt, bezieht sich dieser auf eine Antwort, die nicht existiert.

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

Beide Methoden, die mit _myClass mit _lockObject zusammenarbeiten, sperren, was bedeutet, dass die Ausführung einer der beiden Methoden blockiert, während ein anderer Thread eine der beiden Methoden ausführt.

Das ist ein gültiger Ansatz. Eine andere Möglichkeit besteht darin, MyClass-Thread tatsächlich sicher zu machen, indem entweder gleichzeitige Auflistungen verwendet oder solche Sperren innerhalb dieser Klasse implementiert werden. Auf diese Weise müssen Sie keine lock-Anweisungen in jeder Klasse verwenden, die eine Instanz von MyClass verwendet. Sie können es einfach verwenden und wissen, dass es das intern verwaltet.

0
Scott Hannen