293

从 C# 中的 getter 或 setter 调用异步方法的最优雅的方法是什么?

这是一些伪代码来帮助解释我自己。

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

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

13 回答 13

281

C# 中不允许使用属性没有技术原因。async这是一个有目的的设计决策,因为“异步属性”是矛盾的。

属性应该返回当前值;他们不应该启动后台操作。

通常,当有人想要一个“异步属性”时,他们真正想要的是以下之一:

  1. 返回值的异步方法。在这种情况下,将属性更改为async方法。
  2. 可用于数据绑定但必须异步计算/检索的值。在这种情况下,要么使用async包含对象的工厂方法,要么使用async InitAsync()方法。数据绑定值将default(T)一直持续到该值被计算/检索。
  3. 创建成本高,但应缓存以备将来使用的值。在这种情况下,请AsyncLazy 从我的博客AsyncEx 库中使用。这会给你一个await能干的财产。

更新:我在最近的一篇“异步 OOP”博客文章中介绍了异步属性。

于 2012-12-06T01:16:06.217 回答
119

你不能异步调用它,因为没有异步属性支持,只有异步方法。因此,有两种选择,都利用了 CTP 中的异步方法实际上只是一个返回Task<T>or的方法这一事实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;
    }
}
于 2011-07-06T20:16:50.920 回答
59

由于我的解耦架构,我真的需要调用源自 get 方法。所以我想出了以下实现。

用法: 标题位于 ViewModel 或您可以静态声明为页面资源的对象中。绑定到它,当 getTitle() 返回时,该值将被填充而不会阻塞 UI。

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");
        }
    }
}
于 2011-07-07T16:16:00.773 回答
12

你可以Task这样使用:

public int SelectedTab
        {
            get => selected_tab;
            set
            {
                selected_tab = value;

                new Task(async () =>
                {
                    await newTab.ScaleTo(0.8);
                }).Start();
            }
        }
于 2019-08-02T18:05:59.070 回答
10

我认为我们可以等待只返回第一个 null 的值,然后获取真正的值,所以对于 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();
}
于 2015-04-02T07:15:49.880 回答
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");
        }
    }
}
于 2014-12-01T15:30:48.697 回答
2

由于您的“异步属性”在视图模型中,您可以使用AsyncMVVM

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

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

它将为您处理同步上下文和属性更改通知。

于 2014-12-31T18:28:40.850 回答
0

当我遇到这个问题时,尝试从 setter 或构造函数运行异步方法同步使我陷入 UI 线程的死锁,并且使用事件处理程序需要对一般设计进行太多更改。
解决方案通常是明确地写出我想要隐式发生的事情,即让另一个线程处理操作并让主线程等待它完成:

string someValue=null;
var t = new Thread(() =>someValue = SomeAsyncMethod().Result);
t.Start();
t.Join();

你可能会说我滥用了这个框架,但它确实有效。

于 2020-01-30T17:04:23.943 回答
0

死灵术。
在 .NET Core/NetStandard2 中,您可以使用Nito.AsyncEx.AsyncContext.Run而不是System.Windows.Threading.Dispatcher.InvokeAsync

class AsyncPropertyTest
{

    private static async System.Threading.Tasks.Task<int> GetInt(string text)
    {
        await System.Threading.Tasks.Task.Delay(2000);
        System.Threading.Thread.Sleep(2000);
        return int.Parse(text);
    }


    public static int MyProperty
    {
        get
        {
            int x = 0;

            // https://stackoverflow.com/questions/6602244/how-to-call-an-async-method-from-a-getter-or-setter
            // https://stackoverflow.com/questions/41748335/net-dispatcher-for-net-core
            // https://github.com/StephenCleary/AsyncEx
            Nito.AsyncEx.AsyncContext.Run(async delegate ()
            {
                x = await GetInt("123");
            });

            return x;
        }
    }


    public static void Test()
    {
        System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
        System.Console.WriteLine(MyProperty);
        System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
    }


}

如果您只是选择System.Threading.Tasks.Task.Runor System.Threading.Tasks.Task<int>.Run,那么它将不起作用。

于 2020-02-05T11:14:36.540 回答
0

您可以在属性更改时创建事件并调用事件。像这样的东西:

private event EventHandler<string> AddressChanged;
public YourClassConstructor(){
     AddressChanged += GoogleAddressesViewModel_AddressChanged;
}
private async void GoogleAddressesViewModel_AddressChanged(object sender, string e){
      ... make your async call
}
private string _addressToSearch;
public string AddressToSearch
{
    get { return _addressToSearch; }
    set
    {
        _addressToSearch = value;
        AddressChanged.Invoke(this, AddressToSearch);
    }
}
于 2021-04-13T15:38:55.927 回答
-1

我认为我下面的示例可能遵循@Stephen-Cleary 的方法,但我想给出一个编码示例。这用于数据绑定上下文,例如 Xamarin。

类的构造函数——或者实际上是它所依赖的另一个属性的设置器——可能会调用一个异步 void,它将在任务完成时填充属性,而无需等待或阻塞。当它最终获得一个值时,它将通过 NotifyPropertyChanged 机制更新您的 UI。

我不确定从构造函数调用 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();
    }


}
于 2019-01-30T20:23:39.333 回答
-2

我查看了所有答案,但都有性能问题。

例如在:

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");
        }
    }
}

Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });

使用调度程序,这不是一个好的答案。

但有一个简单的解决方案,只需这样做:

string _Title;
    public string Title
    {
        get
        {
            if (_Title == null)
            {   
                Task.Run(()=> 
                {
                    _Title = getTitle();
                    RaisePropertyChanged("Title");
                });        
                return;
            }
            return _Title;
        }
        set
        {
            if (value != _Title)
            {
                _Title = value;
                RaisePropertyChanged("Title");
            }
        }
    }
于 2020-03-03T12:17:34.600 回答
-5

您可以将属性更改为Task<IEnumerable>

并执行以下操作:

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

并像 await MyList 一样使用它;

于 2018-01-27T01:31:23.430 回答