簡體   English   中英

如何從 getter 或 setter 調用異步方法?

[英]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屬性沒有技術原因。這是一個有目的的設計決定,因為“異步屬性”是一個矛盾詞。

屬性應返回當前值; 他們不應該啟動后台操作。

通常,當有人想要一個“異步屬性”時,他們真正想要的是以下之一:

  1. 返回值的異步方法。 在這種情況下,將屬性更改為async方法。
  2. 可用於數據綁定但必須異步計算/檢索的值。 在這種情況下,要么對包含 object 使用async工廠方法,要么使用async InitAsync()方法。 在計算/檢索該值之前,數據綁定值將是default(T)
  3. 創建成本高昂但應緩存以備將來使用的值。 在這種情況下,請使用我博客中的AsyncLazyAsyncEx 庫 這將為您提供可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.RunSystem.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM