C# 如何从getter或setter调用异步方法?

C# 如何从getter或setter调用异步方法?,c#,async-ctp,C#,Async Ctp,在C#中,从getter或setter调用异步方法最优雅的方式是什么 这里有一些伪代码来帮助解释我自己 async Task<IEnumerable> MyAsyncMethod() { return await DoSomethingAsync(); } public IEnumerable MyList { get { //call MyAsyncMethod() here } } 异步任务MyAsyncMethod() {

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

这里有一些伪代码来帮助解释我自己

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

public IEnumerable MyList
{
    get
    {
         //call MyAsyncMethod() here
    }
}
异步任务MyAsyncMethod() { return wait DoSomethingAsync(); } 公共IEnumerable MyList { 得到 { //在此处调用MyAsyncMethod() } }
您不能异步调用它,因为没有异步属性支持,只有异步方法。因此,有两种选择,都利用了CTP中的异步方法实际上只是返回
Task
Task
的方法这一事实:

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

由于我的解耦体系结构,我确实需要从get方法发起调用。因此,我提出了以下实现

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

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

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

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

  • 返回值的异步方法。在这种情况下,将属性更改为
    async
    方法
  • 可以在数据绑定中使用但必须异步计算/检索的值。在这种情况下,对包含的对象使用
    async
    factory方法,或者使用
    async InitAsync()
    方法。在计算/检索值之前,数据绑定值将为默认值(T)
  • 创建成本很高,但应缓存以供将来使用的值。在这种情况下,请使用
    AsyncLazy
    或。这将为您提供一个
    await
    able属性
  • 更新:我在最近的一篇“async OOP”博客文章中谈到了这一点。

    我认为.GetAwaiter().GetResult()正是解决这个问题的方法,不是吗? 例如:

    由于“异步属性”位于viewmodel中,因此可以使用:

    类MyViewModel:AsyncBindableBase
    {
    公共字符串标题
    {
    得到
    {
    return属性.Get(GetTitleAsync);
    }
    }
    私有异步任务GetTitleAsync()
    {
    //...
    }
    }
    

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

    我认为我们可以等待值先返回null,然后再获取实际值,因此对于纯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();
    }
    

    您可以将权限更改为
    任务

    然后做一些类似的事情:

    get
    {
        Task<IEnumerable>.Run(async()=>{
           return await getMyList();
        });
    }
    
    get
    {
    Task.Run(异步()=>{
    return wait getMyList();
    });
    }
    
    像这样使用它
    等待迈利斯特

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

    类的构造函数(或者实际上是它所依赖的另一个属性的setter)可以调用异步void,该异步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();
        }
    
    
    }
    

    您可以像这样使用
    任务

    public int SelectedTab
            {
                get => selected_tab;
                set
                {
                    selected_tab = value;
    
                    new Task(async () =>
                    {
                        await newTab.ScaleTo(0.8);
                    }).Start();
                }
            }
    

    当我遇到这个问题时,尝试从setter或构造函数运行异步方法同步性使我在UI线程上陷入死锁,并且使用事件处理程序需要在常规设计中进行太多更改。
    解决方案通常是显式编写我希望隐式执行的操作,即让另一个线程处理该操作,并让主线程等待该操作完成:

    string someValue=null;
    var t = new Thread(() =>someValue = SomeAsyncMethod().Result);
    t.Start();
    t.Join();
    
    你可能会说我滥用了这个框架,但它是有效的。

    亡灵巫术
    在.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"));
        }
    
    
    }
    
    类AsyncPropertyTest
    {
    私有静态异步System.Threading.Tasks.Task GetInt(字符串文本)
    {
    等待系统。线程。任务。任务。延迟(2000);
    系统线程线程睡眠(2000);
    返回int.Parse(text);
    }
    公共静态int-MyProperty
    {
    得到
    {
    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(异步委托()
    {
    x=等待GetInt(“123”);
    });
    返回x;
    }
    }
    公共静态无效测试()
    {
    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.Run
    System.Threading.Tasks.Task.Run
    ,那么它将不起作用

    我审查了所有答案,但都存在性能问题

    例如:

    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(异步()=>{Title=await getTitle();})

    使用dispatcher,这不是一个好答案

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

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

    您可以创建事件并在属性更改时调用事件。 大概是这样的:

    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);
        }
    }
    
    私人事件
    
    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"));
        }
    
    
    }
    
    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");
            }
        }
    }
    
    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");
                }
            }
        }
    
    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);
        }
    }
    
        async Task<IEnumerable> MyAsyncMethod()
        {
            return await DoSomethingAsync();
        }
    
        public IEnumerable MyList
        {
            get
            {
                MyAsyncMethod()
                  .ContinueWith((tsk) =>
                  {
                      // what every work 
                  });
            }
        }