Как вызвать асинхронный метод из метода получения или установки? - PullRequest
175 голосов
/ 07 июля 2011

Какой самый элегантный способ вызова асинхронного метода из метода получения или установки в C #?

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

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

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

Ответы [ 8 ]

169 голосов
/ 06 декабря 2012

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

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

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

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

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

88 голосов
/ 07 июля 2011

Вы не можете вызывать его асинхронно, поскольку нет поддержки асинхронных свойств, только асинхронные методы.Таким образом, есть две опции, каждая из которых использует тот факт, что асинхронные методы в 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;
    }
}
46 голосов
/ 07 июля 2011

Мне действительно нужно, чтобы вызов исходил из метода 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");
        }
    }
}
10 голосов
/ 02 апреля 2015

Я думаю, что мы можем дождаться, пока значение вернет сначала ноль, а затем получить реальное значение, поэтому в случае 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 голосов
/ 01 декабря 2014

Я думал, что .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");
        }
    }
}
1 голос
/ 31 декабря 2014

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

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

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

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

0 голосов
/ 30 января 2019

Я думаю, что мой пример ниже может следовать подходу @ 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();
    }


}
0 голосов
/ 27 января 2018

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

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

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

и использовать его как await MyList;

...