在c#中从getter或setter调用异步方法的最优雅的方式是什么?
这里有一些伪代码来帮助我解释。
async Task<IEnumerable> MyAsyncMethod() { return await DoSomethingAsync(); } public IEnumerable MyList { get { //call MyAsyncMethod() here } }
您不能异步调用它,因为没有异步属性支持,只有异步方法。因此,有两个选项,都利用了CTP中的异步方法实际上只是一个返回Task<T>或Task的方法的事实:
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; } }
由于我的解耦架构,我确实需要调用源自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"); } } }
c#中不允许async属性的技术原因不存在。这是一个有目的的设计决策,因为“异步属性”是一个矛盾的说法。
async
属性应该返回当前值;他们不应该开始幕后行动。
通常,当有人想要一个“异步属性”时,他们真正想要的是以下内容之一:
async InitAsync()
default(T)
AsyncLazy
await
更新:我在我最近的一篇“async OOP”博客文章中提到了异步属性。
我认为.GetAwaiter().GetResult()正是这个问题的解决方案,不是吗? 如:< / p >
string _Title; public string Title { get { if (_Title == null) { _Title = getTitle().GetAwaiter().GetResult(); } return _Title; } set { if (value != _Title) { _Title = value; RaisePropertyChanged("Title"); } } }
因为你的“async属性”在一个视图模型中,你可以使用AsyncMVVM:
class MyViewModel : AsyncBindableBase { public string Title { get { return Property.Get(GetTitleAsync); } } private async Task<string> GetTitleAsync() { //... } }
它将负责同步上下文和属性更改通知。
我认为我们可以等待的值只是返回第一个空,然后得到真实的值,所以在纯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(); }
可以将属性更改为Task<IEnumerable>
Task<IEnumerable>
你可以这样做:
get { Task<IEnumerable>.Run(async()=>{ return await getMyList(); }); }
和使用它像 等待MyList; < / p >
我认为我下面的例子可能遵循@Stephen-Cleary的方法,但我想给出一个编码的例子。这适用于数据绑定上下文中,例如Xamarin。
类的构造函数——或者它所依赖的另一个属性的setter——可以调用一个async 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(); } }
你可以像这样使用Task:
public int SelectedTab { get => selected_tab; set { selected_tab = value; new Task(async () => { await newTab.ScaleTo(0.8); }).Start(); } }
string someValue=null; var t = new Thread(() =>someValue = SomeAsyncMethod().Result); t.Start(); t.Join();
你可能会说我滥用了这个框架,但它确实有效。
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.Run或System.Threading.Tasks.Task<int>.Run,那么它将不起作用。
System.Threading.Tasks.Task.Run
System.Threading.Tasks.Task<int>.Run
我审查了所有的答案,但都有一个性能问题。
例如:
Deployment.Current.Dispatcher。InvokeAsync(async () => {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); } }
你可以简单地这样做:
public IEnumerable MyList { get { Task.Run(async () => await MyAsyncMethod); } }
但根据完整的上下文,你可以尝试找到另一种方式来触发你的异步方法,例如:当你调用MyList时,你可以调用第一个MyAsyncMethod..