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

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

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

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


public IEnumerable MyList
{
get
{
//call MyAsyncMethod() here
}
}
194284 次浏览

您不能异步调用它,因为没有异步属性支持,只有异步方法。因此,有两个选项,都利用了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;
}
}

由于我的解耦架构,我确实需要调用源自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属性的技术原因不存在。这是一个有目的的设计决策,因为“异步属性”是一个矛盾的说法。

属性应该返回当前值;他们不应该开始幕后行动。

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

  1. 返回值的异步方法。在这种情况下,将属性更改为async方法。
  2. 可以在数据绑定中使用但必须异步计算/检索的值。在这种情况下,要么为包含对象使用async工厂方法,要么使用async InitAsync()方法。在计算/检索该值之前,数据绑定值将是default(T)
  3. 创建成本很高,但应该缓存以供将来使用的值。在这种情况下,使用AsyncLazy 来自我的博客AsyncEx图书馆。这将给你一个awaitable属性。

更新:我在我最近的一篇“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>

你可以这样做:

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();
}
}
当我遇到这个问题时,试图从setter或构造函数运行异步方法的同步性让我陷入了UI线程上的死锁,并且使用事件处理程序需要在一般设计中进行太多更改。
解决方案通常是,只显式地写我想隐式发生的事情,也就是让另一个线程处理这个操作,并让主线程等待它完成:

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

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

< p > Necromancing。
在.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.RunSystem.Threading.Tasks.Task<int>.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(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..