Асинхронная инициализация свойств + Lazy

184
21 июня 2018, 13:10

В классе ViewModel имеется два булевых свойства, на которые происходит Binding из xaml:

public bool CanPublish => Document.State.CanPublish();
public bool CanCreateToolData => Document.State.CanCreateToolData();

Разметка из xaml:

Visibility="{Binding CanCreateToolData, Converter={StaticResource BooleanToVisibilityConverter}}"
Visibility="{Binding CanPublish, Converter={StaticResource BooleanToVisibilityConverter}}"

Поскольку вычисление статусов достаточно долгий процесс, то у меня в модели внутри методов CanPublish() и CanCreateToolData() используется инициализация некоторых полей через Lazy. Дебаггер показывает следующее:

1) Идём инициализировать свойство CanPublish, натыкаемся на Lazy;

2) Пока Lazy-метод выполняется, начинается инициализация второго свойства CanCreateToolData, которое тоже обращается к тому же самому Lazy-методу. В итоге:

System.InvalidOperationException
HResult=0x80131509
Сообщение = ValueFactory пытается получить доступ к свойству Value этого экземпляра.
Источник = mscorlib
Трассировка стека:
 в System.Lazy`1.CreateValue()
 в System.Lazy`1.LazyInitValue()
 в System.Lazy`1.get_Value()
 в "путь к моему классу"

Вопрос заключается в следующем: почему инициализация свойств производится асинхронно? Это из-за Lazy-методов? Ошибка, как я понимаю, заключается в том, что идёт обращение к одному и тому же Lazy-методу повторно, пока он ещё не закончил своё выполнение. Есть ли возможность как-то явно указать, что эти свойства должны инициализироваться синхронно?

Спасибо за внимание.

Дополню код Lazy-методом: Тут обращение к свойству ClassConfig

    public bool CanCreateToolData()
    {
        ..
        if (ver.Services.Interpretator.ClassConfig == null) { return false; }
        ...
    }
    public bool CanPublish()
    {
        ..
        if (ver.Services.Interpretator.ClassConfig == null) { return false; }
        ...
    }

А тут вызов метода Lazy:

// поле класса, InvalidOperationException происходит при обращении к mClassDefiner.Value
public IClassDefiner ClassConfig => mClassDefiner.Value;
public Interpretator()
{    
    mClassDefiner = new Lazy<IClassDefiner>(GetClassDefiner);
}

private IClassDefiner GetClassDefiner()
{
  // ресурсоёмкая операция
}
Answer 1

Рассмотрим пример:

Есть класс с Lazy полем

public class Foo
{
    public Lazy<int> Lazy = new Lazy<int>(() =>
    {
        Console.WriteLine($"{DateTime.UtcNow} - {Thread.CurrentThread.ManagedThreadId} - LAZY ENTER");
        Thread.Sleep(5000);
        Console.WriteLine($"{DateTime.UtcNow} - {Thread.CurrentThread.ManagedThreadId} - LAZY EXIT");
        return 10;
    });
}

Мы логгируем входы и выходы из функции создания для поля. Далее, запустим параллельно 10 тредов и попробуем обратиться к нашему полю

Console.WriteLine($"{DateTime.UtcNow} - {Thread.CurrentThread.ManagedThreadId} - START");
var target = new Foo();
var threads = Enumerable.Range(0, 10).Select(x =>
{
    var thread = new Thread(() =>
    {
        Console.WriteLine($"{DateTime.UtcNow} - {Thread.CurrentThread.ManagedThreadId} - TASK {x} ENTER");
        var value = target.Lazy.Value;
        Console.WriteLine($"{DateTime.UtcNow} - {Thread.CurrentThread.ManagedThreadId} - TASK {x} EXIT");
    });
    thread.Start();
    return thread;
}).ToArray();
foreach (var t in threads) t.Join();
Console.WriteLine($"{DateTime.UtcNow} - {Thread.CurrentThread.ManagedThreadId} - END");

Вывод в консоль предсказуем: все треды ждут функцию сооздания, и после этого завершаются без исключений

31.05.2018 15:05:55 - 33 - START
31.05.2018 15:05:55 - 32 - TASK 0 ENTER
31.05.2018 15:05:55 - 31 - TASK 1 ENTER
31.05.2018 15:05:55 - 32 - LAZY ENTER
31.05.2018 15:05:55 - 30 - TASK 2 ENTER
31.05.2018 15:05:55 - 29 - TASK 3 ENTER
31.05.2018 15:05:55 - 28 - TASK 4 ENTER
31.05.2018 15:05:55 - 27 - TASK 5 ENTER
31.05.2018 15:05:55 - 26 - TASK 6 ENTER
31.05.2018 15:05:55 - 25 - TASK 7 ENTER
31.05.2018 15:05:55 - 19 - TASK 8 ENTER
31.05.2018 15:05:55 - 18 - TASK 9 ENTER
31.05.2018 15:06:00 - 32 - LAZY EXIT
31.05.2018 15:06:00 - 32 - TASK 0 EXIT
31.05.2018 15:06:00 - 31 - TASK 1 EXIT
31.05.2018 15:06:00 - 29 - TASK 3 EXIT
31.05.2018 15:06:00 - 30 - TASK 2 EXIT
31.05.2018 15:06:00 - 28 - TASK 4 EXIT
31.05.2018 15:06:00 - 25 - TASK 7 EXIT
31.05.2018 15:06:00 - 26 - TASK 6 EXIT
31.05.2018 15:06:00 - 19 - TASK 8 EXIT
31.05.2018 15:06:00 - 27 - TASK 5 EXIT
31.05.2018 15:06:00 - 18 - TASK 9 EXIT
31.05.2018 15:06:00 - 33 - END    

А та ошибка, что вы указываете - это да, это именно проблема, когда вы в процессе подсчета значения, прямо внутри вашей ресурсоемкой операции, пытаетесь рекурсивно это значение получить.

Воспроизвести очень просто.

Lazy<int> value = null;
value = new Lazy<int>(()=>value.Value); 
Console.WriteLine(value.Value);

на выходе будет исключение ValueFactory пытается получить доступ к свойству Value этого экземпляра., так как невозможно посчитать, чем равно Value, если вы внутри этого подсчета пытаетесь его использовать.

READ ALSO
Как написать на C# свой HtmlHelpers?

Как написать на C# свой HtmlHelpers?

Хочу сделать свои HtmlHelper, но так что бы первый екстеншен, после Html, был мой, а после этого уже HtmlHelper, как в Kendo

167
C# Кодировка Uri в WebRequest

C# Кодировка Uri в WebRequest

url содержит кириллицу, изначально закодированную в HEX - %20%2B%A0 и тдТак вот если сделать WebRequest

157
EntityValidationErrors при добавлении

EntityValidationErrors при добавлении

Была взята за основу готовая база данных и по ней создан контекст и модели таблицСвязал таблицы в конструкторе таблиц таким образом: countrylanguage...

236
Сохранить график Chart C# в PDF

Сохранить график Chart C# в PDF

Как сохранить график, построенный с помощью Chart в PDF формате?

157