如何从 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<T>
类型的属性,它将立即返回,具有正常的属性语义,并且仍然允许根据需要异步处理事物。
@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。这将为您提供await
able 属性。
更新:我在最近的一篇“异步 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<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;
【讨论】:
感谢您的回复。选项 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.Run
或System.Threading.Tasks.Task<int>.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<IEnumerable>
然后做一些类似的事情:
get
Task<IEnumerable>.Run(async()=>
return await getMyList();
);
并像使用它一样 等待我的列表;
【讨论】:
以上是关于如何从 getter 或 setter 调用异步方法?的主要内容,如果未能解决你的问题,请参考以下文章
GroovyGroovy 方法调用 ( Java 类成员及 setter 和 getter 方法设置 | Groovy 类自动生成成员的 getter 和 setter 方法 )