[英]How to call an async method from a getter or setter?
從 C# 中的 getter 或 setter 調用異步方法的最優雅方法是什么?
這里有一些偽代碼來幫助解釋我自己。
async Task<IEnumerable> MyAsyncMethod()
{
return await DoSomethingAsync();
}
public IEnumerable MyList
{
get
{
//call MyAsyncMethod() here
}
}
C# 中不允許使用async
屬性沒有技術原因。這是一個有目的的設計決定,因為“異步屬性”是一個矛盾詞。
屬性應返回當前值; 他們不應該啟動后台操作。
通常,當有人想要一個“異步屬性”時,他們真正想要的是以下之一:
async
方法。async
工廠方法,要么使用async InitAsync()
方法。 在計算/檢索該值之前,數據綁定值將是default(T)
。AsyncLazy
或AsyncEx 庫。 這將為您提供可await
的財產。更新:我在最近的一篇“異步 OOP”博文中介紹了異步屬性。
您不能異步調用它,因為沒有異步屬性支持,只有異步方法。 因此,有兩種選擇,都利用了 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 或 object 中,您可以靜態聲明為頁面資源。 綁定到它,當 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");
}
}
}
您可以像這樣使用Task
:
public int SelectedTab
{
get => selected_tab;
set
{
selected_tab = value;
new Task(async () =>
{
await newTab.ScaleTo(0.8);
}).Start();
}
}
我認為我們可以等待首先返回的值 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();
}
我認為 .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");
}
}
}
由於您的“異步屬性”在視圖模型中,因此您可以使用AsyncMVVM :
class MyViewModel : AsyncBindableBase
{
public string Title
{
get
{
return Property.Get(GetTitleAsync);
}
}
private async Task<string> GetTitleAsync()
{
//...
}
}
它將為您處理同步上下文和屬性更改通知。
當我遇到這個問題時,嘗試從 setter 或構造函數同步運行異步方法讓我陷入了 UI 線程的死鎖,並且使用事件處理程序需要在總體設計中進行太多更改。
通常,解決方案是明確地寫出我希望隱式發生的事情,即讓另一個線程處理該操作並讓主線程等待它完成:
string someValue=null;
var t = new Thread(() =>someValue = SomeAsyncMethod().Result);
t.Start();
t.Join();
你可能會說我濫用了這個框架,但它確實有效。
您可以創建事件並在屬性更改時調用事件。 是這樣的:
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);
}
}
死靈法術。
在 .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.Run
或System.Threading.Tasks.Task<int>.Run
,那么它不會起作用。
從 C# 中的 getter 或 setter 調用異步方法的最優雅的方法是什么?
這是一些偽代碼來幫助解釋我自己。
async Task<IEnumerable> MyAsyncMethod()
{
return await DoSomethingAsync();
}
public IEnumerable MyList
{
get
{
//call MyAsyncMethod() here
}
}
你可以簡單地這樣做:
public IEnumerable MyList
{
get
{
Task.Run(async () => await MyAsyncMethod);
}
}
但是根據完整的上下文,您可以嘗試找到另一種觸發異步方法的方法,例如:當您調用 MyList 時,您可以先調用 MyAsyncMethod ..
我認為下面的示例可能遵循@Stephen-Cleary 的方法,但我想給出一個編碼示例。 這用於數據綁定上下文,例如 Xamarin。
class 的構造函數——或者實際上它所依賴的另一個屬性的設置器——可以調用一個異步 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();
}
}
我審查了所有答案,但都有性能問題。
例如:
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");
}
}
}
您可以繼續使用它
async Task<IEnumerable> MyAsyncMethod()
{
return await DoSomethingAsync();
}
public IEnumerable MyList
{
get
{
MyAsyncMethod()
.ContinueWith((tsk) =>
{
// what every work
});
return _myList
}
}
您可以將屬性更改為Task<IEnumerable>
並做類似的事情:
get
{
Task<IEnumerable>.Run(async()=>{
return await getMyList();
});
}
並像 await MyList; 一樣使用它;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.