Лучший способ справиться с асинхронным вызовом и ждать в установщике свойств в С#. Изучите его простым способом.

В C# вы можете использовать ключевые слова async и await для вызова асинхронного метода внутри установщика свойства. Вот пример:

private List<Asset> _assets;
public List<Asset> Assets
{
    get => _assets;
    set => SetAssetsAsync(value);
}

private async Task SetAssetsAsync(List<Asset> value)
{
    _assets = value;
    composition = await GetCompositionAsync();
}

В этом примере мы пометили метод SetAssetsAsync как асинхронный и использовали ключевое слово await для ожидания завершения метода GetCompositionAsync перед назначением переменной состава. Затем мы вызываем этот метод внутри установщика свойства после присвоения нового значения полю _assets.

Обратите внимание, что мы изменили установщик свойства на лямбда-выражение, которое просто вызывает метод SetAssetsAsync с новым значением. Это позволяет нам использовать ключевое слово await внутри метода, чтобы дождаться завершения асинхронного метода.

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

Но вот проблема, мы не ждем асинхронного вызова в установщике свойства.

В C# вы можете использовать метод NotifyCompletion для асинхронной установки значения свойства и уведомления вызывающего кода о завершении операции. Вот пример:

private List<Asset> _assets;
public List<Asset> Assets
{
    get => _assets;
    set => new ValueTask(SetAssetsAsync(value)).AsTask().ConfigureAwait(false);
}

private async Task SetAssetsAsync(List<Asset> value)
{
    _assets = value;
    composition = await GetCompositionAsync();
}

public struct ValueTask : INotifyCompletion
{
    private readonly Task _task;

    public ValueTask(Task task)
    {
        _task = task;
    }

    public ValueTaskAwaiter GetAwaiter() => new ValueTaskAwaiter(_task);

    public void OnCompleted(Action continuation) => _task.ContinueWith(_ => continuation());

    public struct ValueTaskAwaiter : INotifyCompletion
    {
        private readonly TaskAwaiter _awaiter;

        public ValueTaskAwaiter(Task task)
        {
            _awaiter = task.GetAwaiter();
        }

        public bool IsCompleted => _awaiter.IsCompleted;

        public void OnCompleted(Action continuation) => _awaiter.OnCompleted(continuation);

        public void GetResult() => _awaiter.GetResult();
    }
}

В этом примере мы определили пользовательскую структуру с именем ValueTask, которая реализует интерфейс INotifyCompletion. Это позволяет нам использовать метод NotifyCompletion для асинхронной установки значения свойства и уведомления вызывающего кода о завершении операции.

Внутри установщика свойства Assets мы создаем новый экземпляр структуры ValueTask и передаем метод SetAssetsAsync в качестве параметра. Затем мы вызываем метод ConfigureAwait(false), чтобы убедиться, что продолжение выполняется в потоке пула потоков, а не в вызывающем потоке, что может помочь избежать взаимоблокировок в определенных сценариях.

Метод SetAssetsAsync помечается как асинхронный и задает для поля _assets новое значение. Затем он вызывает метод GetCompositionAsync и ожидает результата, прежде чем присвоить его переменной композиции.

Структура ValueTask определяет вложенную структуру с именем ValueTaskAwaiter, которая реализует интерфейс INotifyCompletion. Это позволяет нам использовать метод NotifyCompletion для уведомления вызывающего кода о завершении операции.

Важно отметить, что использование NotifyCompletion может быть более сложным и подверженным ошибкам, чем использование async и await, поскольку оно требует ручной реализации интерфейса INotifyCompletion и вложенных структур. Как правило, лучше использовать ключевые слова async и await, когда это возможно, чтобы упростить код и избежать потенциальных ошибок.