Подтвердить что ты не робот

Lazy <T> ExecutionAndPublication - примеры, которые могут вызвать тупик

В документации для LazyThreadSafetyMode указано, что использование значения ExecutionAndPublication может вызвать взаимоблокировки, если метод инициализации (или конструктор по умолчанию, если есть нет метода инициализации) использует внутренние блокировки. Я пытаюсь лучше понять примеры, которые могут вызвать тупик при использовании этого значения. При использовании этого значения я инициализирую ChannelFactory. Я не вижу конструктора ChannelFactory, использующего любые внутренние блокировки (просматривая класс с Reflector), поэтому я считаю, что этот сценарий не соответствует возможной ситуации взаимоблокировки, но мне любопытно, какие ситуации могут вызвать тупик, а также если возможно тупик, инициализирующий ChannelFactory.

Итак, вкратце, мои вопросы:

  • Можно ли вызвать тупик, инициализирующий ChannelFactory с помощью ExecutionAndPublication?

  • Каковы возможные способы заставить тупик инициализировать другие объекты с помощью ExecutionAndPublication?

Предположим, что у вас есть следующий код:

class x
{
   static Lazy<ChannelFactory<ISomeChannel>> lcf = 
        new Lazy<ChannelFactory<ISomeChannel>>(
        () => new ChannelFactory<ISomeChannel>("someEndPointConfig"), 
        LazyThreadSafetyMode.ExecutionAndPublication
        );

    public static ISomeChannel Create()
    {
        return lcf.Value.CreateChannel();
    }
}
4b9b3361

Ответ 1

  • Это как задокументировано - если он не использует никаких блокировок, это использование не может вызвать каких-либо взаимоблокировок.
  • Представьте, что у вас есть ленивое значение, которое вы инициализируете путем чтения из базы данных, но вы хотите, чтобы в любой момент обращался к БД только одному потоку. Если у вас есть другой код, который обращается к БД, у вас может быть тупик. Рассмотрим следующий код:
void Main()
{
    Task otherThread = Task.Factory.StartNew(() => UpdateDb(43));
    Thread.Sleep(100);
    Console.WriteLine(lazyInt.Value);
}

static object l = new object();
Lazy<int> lazyInt = new Lazy<int>(Init, LazyThreadSafetyMode.ExecutionAndPublication);

static int Init()
{
    lock(l)
    {
        return ReadFromDb();
    }
}

void UpdateDb(int newValue)
{
    lock(l)
    {
        // to make sure deadlock occurs every time
        Thread.Sleep(1000);

        if (newValue != lazyInt.Value)
        {
            // some code that requires the lock
        }
    }
}

Init() считывает из БД, поэтому он должен использовать блокировку. UpdateDb() записывается в БД, поэтому ему тоже нужна блокировка, и поскольку Lazy в этом случае также использует блокировку внутри, он вызывает тупик.

В этом случае было бы легко устранить тупик, перемещая доступ к lazyInt.Value в UpdateDb() вне оператора блокировки, но это может быть не так тривиально (или очевидно) в других случаях.