Как вызвать метод асинхронизации с геттера или сеттера?

Что было бы самым элегантным способом вызова метода асинхронного метода из getter или setter в С#?

Вот какой-то псевдокод, который поможет мне объяснить.

async Task<IEnumerable> MyAsyncMethod()
{
    return await DoSomethingAsync();
}

public IEnumerable MyList
{
    get
    {
         //call MyAsyncMethod() here
    }
}

Ответ 1

Мне действительно нужен был вызов, чтобы начать с метода get из-за моей развязанной архитектуры. Поэтому я придумал следующую реализацию.

Использование: Заголовок находится в ViewModel или объекте, который вы можете статически объявить как ресурс страницы. Привяжите к нему, и значение будет заполнено без блокировки пользовательского интерфейса, когда getTitle() вернется.

string _Title;
public string Title
{
    get
    {
        if (_Title == null)
        {   
            Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
        }
        return _Title;
    }
    set
    {
        if (value != _Title)
        {
            _Title = value;
            RaisePropertyChanged("Title");
        }
    }
}

Ответ 2

Нет технической причины, по которой async свойства не разрешены в С#. Это было целенаправленное дизайнерское решение, потому что "асинхронные свойства" - это оксюморон.

Свойства должны возвращать текущие значения; они не должны начинать фоновые операции.

Обычно, когда кто-то хочет "асинхронное свойство", он действительно хочет получить одно из следующих:

  1. Асинхронный метод, который возвращает значение. В этом случае измените свойство на async метод.
  2. Значение, которое может использоваться в привязке данных, но должно вычисляться/извлекаться асинхронно. В этом случае либо используйте async метод фабрики для содержащего объекта, либо используйте async InitAsync(). Значение, связанное с данными, будет использоваться по default(T) до тех пор, пока значение не будет вычислено/получено.
  3. Значение, которое дорого создать, но должно быть кэшировано для будущего использования. В этом случае используйте AsyncLazy из моего блога или библиотеки AsyncEx. Это даст вам await собственность.

Обновление: я рассматриваю асинхронные свойства в одной из моих последних публикаций в блоге "async OOP".

Ответ 3

Вы не можете называть это асинхронно, поскольку поддержка асинхронных свойств отсутствует, только методы async. Таким образом, есть два варианта, оба из которых используют тот факт, что асинхронные методы в CTP - это просто метод, который возвращает Task<T> или Task:

// Make the property return a Task<T>
public Task<IEnumerable> MyList
{
    get
    {
         // Just call the method
         return MyAsyncMethod();
    }
}

Или:

// Make the property blocking
public IEnumerable MyList
{
    get
    {
         // Block via .Result
         return MyAsyncMethod().Result;
    }
}

Ответ 4

Я думаю, что мы можем ожидать, что значение просто вернет первый нуль, а затем получит реальное значение, поэтому в случае Pure MVVM (например, проект PCL) я считаю, что следующее наиболее элегантное решение:

private IEnumerable myList;
public IEnumerable MyList
{
  get
    { 
      if(myList == null)
         InitializeMyList();
      return myList;
     }
  set
     {
        myList = value;
        NotifyPropertyChanged();
     }
}

private async void InitializeMyList()
{
   MyList = await AzureService.GetMyList();
}

Ответ 5

Я думал: GetAwaiter(). GetResult() был именно решением этой проблемы, нет? например:

string _Title;
public string Title
{
    get
    {
        if (_Title == null)
        {   
            _Title = getTitle().GetAwaiter().GetResult();
        }
        return _Title;
    }
    set
    {
        if (value != _Title)
        {
            _Title = value;
            RaisePropertyChanged("Title");
        }
    }
}

Ответ 6

Поскольку ваше свойство "async" находится в режиме просмотра, вы можете использовать AsyncMVVM:

class MyViewModel : AsyncBindableBase
{
    public string Title
    {
        get
        {
            return Property.Get(GetTitleAsync);
        }
    }

    private async Task<string> GetTitleAsync()
    {
        //...
    }
}

Он позаботится о вашем синхронном контексте и уведомлении об изменении свойства.

Ответ 7

Я думаю, что мой пример ниже может следовать подходу @Stephen-Cleary, но я хотел бы привести закодированный пример. Это для использования в контексте привязки данных, например, Xamarin.

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

Я не уверен в каких-либо побочных эффектах вызова aysnc void из конструктора. Возможно, комментатор остановится на обработке ошибок и т.д.

class MainPageViewModel : INotifyPropertyChanged
{
    IEnumerable myList;

    public event PropertyChangedEventHandler PropertyChanged;

    public MainPageViewModel()
    {

        MyAsyncMethod()

    }

    public IEnumerable MyList
    {
        set
        {
            if (myList != value)
            {
                myList = value;

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("MyList"));
                }
            }
        }
        get
        {
            return myList;
        }
    }

    async void MyAsyncMethod()
    {
        MyList = await DoSomethingAsync();
    }


}

Ответ 8

Следует отметить: В случае возвращения результата IEnumerable доходность yield имеет лучшую производительность и не требует преобразования в IEnumerable.

public IEnumerable MyList
    {
        get
        {
            yield return MyAsyncMethod();
        }
    }

Ответ 9

Вы можете изменить свойство на Task<IEnumerable>

и сделать что-то вроде:

get
{
    Task<IEnumerable>.Run(async()=>{
       return await getMyList();
    });
}

и используйте его как ожидание MyList;