Использование статического свойства get only thread thread safe? - PullRequest
0 голосов
/ 11 мая 2018

У меня есть этот класс:

class MyFoo
{
    private static readonly string _foo = InitFoo();

    public static string Foo
    {
        get
        {
            return _foo;
        }
    }

    private static string InitFoo()
    {
        Debug.WriteLine("InitFoo"); 
        // do some job
        return "Foo";
    }
}

Закрытый статический _foo элемент инициализируется только один раз, когда есть ссылка на MyFoo.Foo.

Данные, возвращаемые из InitFoo(), велики, и метод может занимать много времени (максимум 1-2 секунды). Мой вопрос, есть ли вероятность, что в то время как поток ссылается на MyFoo.Foo другой поток, который ссылается на он получит незавершенные или неинициализированные данные обратно, поскольку InitFoo() еще не завершено?

Другими словами, является ли указанное выше потокобезопасным? если нет, то как сделать его потокобезопасным (если возможно, избежать объекта блокировки?)

Спасибо.


РЕДАКТИРОВАТЬ: после комментариев о Lazy<T> теперь это лучше для безопасности потоков?:

public sealed class MyFoo
{
    // Explicit static constructor to tell C# compiler not to mark type as beforefieldinit        
    static MyFoo() { }

    private static readonly Lazy<string> _foo = InitFoo();

    public static string Foo
    {
        get
        {
            return _foo.Value;
        }
    }

    private static Lazy<string> InitFoo()
    {
        string s = "Foo";
        return new Lazy<string>(() => s);
    }
}

1 Ответ

0 голосов
/ 11 мая 2018

Есть ли вероятность, что в то время, как поток ссылается на MyFoo.Foo, другой поток, ссылающийся на него, получит незавершенные или неинициализированные данные обратно, так как InitFoo () еще не завершен?

Нет.Инициализация типа является поточно-ориентированной:

  • Никакие другие потоки не могут использовать ваш тип, пока он инициализируется другим потоком
  • Все записи в память, выполняемые потоком инициализации, становятся видимыми длядругие потоки, когда инициализация была выполнена

Есть одна складка, которая заключается в том, что если тот же поток, который инициализирует MyFoo, заканчивает чтение MyFoo._foo до того, как завершит инициализацию, чтовызовет проблему.Это может быть особенно неудобно для диагностики, если есть типы, которые зависят друг от друга для инициализации в цикле.

Вот пример с двумя инициализаторами типов, каждый из которых использует значение от другого.У них обоих есть статические конструкторы, чтобы сделать поведение детерминированным.(Правила того, когда типы инициализируются, зависят от того, имеют ли они статические конструкторы.)

using System;

public class Program 
{
    public static void Main(string[] args)
    {
        // Determine which type to initialize first based on whether there
        // are any command line arguemnts.
        if (args.Length > 0)
        {
            Class2.DoNothing();
        }
        Console.WriteLine($"Class1.Value1: {Class1.Value1}"); 
        Console.WriteLine($"Class2.Value2: {Class2.Value2}"); 
    }
}

public class Class1
{
    public static readonly string Value1 =
        $"When initializing Class1.Value1, Class2.Value2={Class2.Value2}";

    static Class1() {}
}

public class Class2
{
    public static readonly string Value2 =
        $"When initializing Class2.Value2, Class2.Value2={Class1.Value1}";

    static Class2() {}

    public static void DoNothing() {}
}

При запуске без каких-либо аргументов командной строки Class1 начинает инициализацию первой, что, в свою очередь, инициализирует Class2:

Class1.Value1: When initializing Class1.Value1, Class2.Value2=When initializing Class2.Value2, Class2.Value2=
Class2.Value2: When initializing Class2.Value2, Class2.Value2=

С любым аргументом командной строки мы сначала инициализируем Class2, что, в свою очередь, инициализирует Class1:

Class1.Value1: When initializing Class1.Value1, Class2.Value2=
Class2.Value2: When initializing Class2.Value2, Class2.Value2=When initializing Class1.Value1, Class2.Value2=
...