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

Posted

技术标签:

【中文标题】如何从 getter 或 setter 调用异步方法?【英文标题】:How to call an async method from a getter or setter? 【发布时间】:2011-09-29 22:37:32 【问题描述】:

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

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

async Task<IEnumerable> MyAsyncMethod()

    return await DoSomethingAsync();


public IEnumerable MyList

    get
    
         //call MyAsyncMethod() here
    

【问题讨论】:

我的问题是为什么。属性应该模仿类似于字段的东西,因为它通常应该执行很少(或至少非常快速)的工作。如果你有一个长期运行的属性,最好把它写成一个方法,这样调用者就知道它是一个更复杂的工作体。 @James:完全正确——我怀疑这就是 CTP 明确不支持的原因。话虽如此,您始终可以创建Task&lt;T&gt; 类型的属性,它将立即返回,具有正常的属性语义,并且仍然允许根据需要异步处理事物。 @James 我的需求源于使用 Mvvm 和 Silverlight。我希望能够绑定到一个属性,其中数据的加载是延迟完成的。我使用的 ComboBox 扩展类需要在 InitializeComponent() 阶段进行绑定,但实际数据加载要晚得多。在尝试用尽可能少的代码完成任务时,getter 和 async 感觉就像是完美的组合。 相关:***.com/a/33942013/11635 我知道您的示例返回一个 Enumerable,但如果您的异步方法只返回一个 Task,您可以将其更改为返回 void 并从 setter 调用它。 【参考方案1】:

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

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

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

    返回值的异步方法。在这种情况下,请将属性更改为 async 方法。 可用于数据绑定但必须异步计算/检索的值。在这种情况下,要么对包含对象使用 async 工厂方法,要么使用 async InitAsync() 方法。在计算/检索该值之前,数据绑定值将是 default(T)。 创建成本高,但应缓存以备将来使用的值。在这种情况下,请使用 AsyncLazy from my blog 或 AsyncEx library。这将为您提供awaitable 属性。

更新:我在最近的一篇“异步 OOP”博文中介绍了 asynchronous properties。

【讨论】:

在第 2 点中。恕我直言,您没有考虑设置属性应该再次初始化基础数据(不仅在构造函数中)的常规场景。除了使用 Nito AsyncEx 或使用Dispatcher.CurrentDispatcher.Invoke(new Action(..)之外还有其他方法吗? @Gerard:我不明白为什么第 (2) 点在这种情况下不起作用。只需实现INotifyPropertyChanged,然后在异步更新进行时决定是否要返回旧值或default(T) @Stephan:好的,但是当我在设置器中调用异步方法时,我收到 CS4014“未等待”警告(或者仅在 Framework 4.0 中?)。在这种情况下,您是否建议取消该警告? @Gerard:我的第一个建议是使用NotifyTaskCompletion from my AsyncEx project。或者你可以建立自己的;没那么难。 @Stephan:好的,我会试试的。也许一篇关于这个异步数据绑定视图模型场景的好文章已经到位。例如。绑定到Binding PropName.Result 对我来说并非易事。【参考方案2】:

你不能异步调用它,因为没有异步属性支持,只有异步方法。因此,有两种选择,都利用了 CTP 中的异步方法实际上只是返回 Task&lt;T&gt;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;
    

【讨论】:

感谢您的回复。选项 A:返回任务并不能真正用于绑定目的。选项 B:.Result,正如您所提到的,会阻塞 UI 线程(在 Silverlight 中),因此需要在后台线程上执行操作。我会看看我能不能用这个想法想出一个可行的解决方案。 @duluca:您也可以尝试使用类似private async void SetupList() MyList = await MyAsyncMethod(); 的方法,这将导致在异步操作完成后立即设置 MyList(然后自动绑定,如果它实现 INPC)。 . 该属性需要在一个我声明为页面资源的对象中,所以我真的需要这个调用来自 getter。请参阅我的答案以了解我想出的解决方案。 @duluca:实际上,这就是我建议您执行的操作...但是请意识到,如果您快速多次访问 Title,您当前的解决方案将导致多次调用 getTitle()同时... 非常好。虽然对于我的具体情况不是问题,但对 isLoading 进行布尔检查可以解决问题。【参考方案3】:

由于我的解耦架构,我真的需要调用源自 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");
        
    

【讨论】:

updfrom 18/07/2012 在 Win8 RP 中,我们应该将 Dispatcher 调用更改为:Window.Current.CoreWindow.Dispatcher.RunAsync( CoreDispatcherPriority.Normal, async ( ) => Title= await GetTytleAsync(url);); @ChristopherStevenson,我也这么认为,但我不相信是这样。因为 getter 被执行为一劳永逸,所以在完成时不调用 setter,当 getter 完成执行时绑定将不会更新。 不,它有并且有一个竞争条件,但用户不会看到它,因为'RaisePropertyChanged("Title")'。它在完成之前返回。但是,完成后,您将设置属性。这会触发 PropertyChanged 事件。 Binder 再次获取属性值。 基本上,第一个getter会返回一个空值,然后它会被更新。请注意,如果我们希望每次都调用 getTitle,则可能存在错误循环。 您还应该知道,该异步调用中的任何异常都将被完全吞没。如果你有一个,它们甚至不会到达应用程序上未处理的异常处理程序。【参考方案4】:

你可以像这样使用Task

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

                new Task(async () =>
                
                    await newTab.ScaleTo(0.8);
                ).Start();
            
        

【讨论】:

那么当它抛出异常时就会进入黑洞【参考方案5】:

我认为我们可以等待只返回第一个 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();

【讨论】:

这不会产生编译器警告CS4014: Async method invocation without an await expression 对遵循这个建议持非常的怀疑。观看此视频,然后自己决定:channel9.msdn.com/Series/Three-Essential-Tips-for-Async/…。 你应该避免使用“async void”方法! 每一次呼喊都应该有一个明智的回答,你能@SuperJMN解释一下为什么吗? @Contango 好视频。他说,“仅将async void 用于***处理程序等”。我认为这可能符合“和他们的同类”的条件。【参考方案6】:

我认为 .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");
        
    

【讨论】:

这与使用.Result 阻塞相同——它不是异步的,它可能导致死锁。 你需要添加 IsAsync=True 感谢对我的回答的反馈;我真的很想有人提供这个死锁的例子,这样我就可以看到它的实际效果【参考方案7】:

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

class MyViewModel : AsyncBindableBase

    public string Title
    
        get
        
            return Property.Get(GetTitleAsync);
        
    

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

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

【讨论】:

作为财产,它必须是。 抱歉,我可能错过了这段代码的重点。能详细点吗? 属性被定义为阻塞。 GetTitleAsync() 用作没有语法糖的“异步 getter”。 @DmitryShechtman:不,它不必阻塞。这正是更改通知和状态机的用途。从定义上讲,它们并没有阻塞。根据定义,它们是同步的。这与阻塞不同。 “阻塞”意味着他们可能会做繁重的工作并且可能会花费大量时间来执行。反过来,这正是属性不应该做的事情。【参考方案8】:

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

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

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

【讨论】:

【参考方案9】:

死灵术。 在 .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://***.com/questions/6602244/how-to-call-an-async-method-from-a-getter-or-setter
            // https://***.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.RunSystem.Threading.Tasks.Task&lt;int&gt;.Run,那么它不会起作用。

【讨论】:

什么是Nito.AsyncEx.AsyncContext.Run?我在哪里可以找到它?是外部依赖吗?这些是您应该编辑到此答案中以使其更好的重要细节。 仅供参考:Nito.AsyncEx 是 Stephen Cleary 的库,他之前在此线程的回复(或评论)中链接到该库。这是当前的 github 链接。 github.com/StephenCleary/AsyncEx【参考方案10】:

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

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

【讨论】:

【参考方案11】:

我认为下面的示例可能遵循@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();
    



【讨论】:

调用 async void 是个糟糕的主意,因为它不会阻塞,任何异常都会进入黑洞 @rolls 避免的好理由。仍然只是看,我看不到批准或赞成的编码示例。我很乐意为 ref 提供一个很好的答案并投票。【参考方案12】:

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

例如:

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

【讨论】:

如果你的函数是异步的,使用 getTitle().wait() 而不是 getTitle() 如果我可以让你的函数连续 100 倍。它将产生 100 个线程,从而破坏应用程序的性能 所以在你的情况下,你可以使用任务内部的锁...... 更糟。它会产生 100 个任务,然后将它们全部阻止。【参考方案13】:

您可以将属性更改为Task&lt;IEnumerable&gt;

然后做一些类似的事情:

get

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

并像使用它一样 等待我的列表;

【讨论】:

以上是关于如何从 getter 或 setter 调用异步方法?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法从getter或setter直接访问属性的属性?

如何在多个java类中自动修改代码?

如果该属性在派生类中被覆盖,如何调用基类的属性?

GroovyGroovy 方法调用 ( Java 类成员及 setter 和 getter 方法设置 | Groovy 类自动生成成员的 getter 和 setter 方法 )

使用 Await 读取异步对象集

sequelize getter 和 setter 是如何工作的?