Bagaimana cara memanggil metode async dari pengambil atau penyetel?

223

Apa cara paling elegan untuk memanggil metode async dari pengambil atau penyetel di C #?

Berikut ini beberapa pseudo-code untuk membantu menjelaskan diri saya.

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

public IEnumerable MyList
{
    get
    {
         //call MyAsyncMethod() here
    }
}
Doguhan Uluca
sumber
4
Pertanyaan saya adalah mengapa. Properti seharusnya meniru sesuatu seperti bidang yang biasanya harus melakukan sedikit (atau setidaknya sangat cepat) pekerjaan. Jika Anda memiliki properti yang sudah lama berjalan, lebih baik menuliskannya sebagai metode sehingga penelepon tahu bahwa itu adalah pekerjaan yang lebih kompleks.
James Michael Hare
@ James: Benar sekali - dan saya menduga itu sebabnya ini secara eksplisit tidak didukung dalam CTP. Yang sedang berkata, Anda selalu dapat membuat properti tipe Task<T>, yang akan segera kembali, memiliki semantik properti normal, dan masih memungkinkan hal-hal diperlakukan secara asinkron sesuai kebutuhan.
Reed Copsey
17
@ James Kebutuhan saya muncul karena menggunakan Mvvm dan Silverlight. Saya ingin dapat mengikat ke properti, tempat pemuatan data dilakukan dengan malas. Kelas ekstensi ComboBox yang saya gunakan membutuhkan pengikatan yang terjadi pada tahap InitializeComponent (), namun pemuatan data aktual terjadi jauh lebih lambat. Dalam mencoba menyelesaikan dengan kode sesedikit mungkin, pengambil dan async terasa seperti kombinasi sempurna.
Doguhan Uluca
terkait: stackoverflow.com/a/33942013/11635
Ruben Bartelink
James dan Reed, Anda sepertinya lupa bahwa selalu ada kasus tepi. Dalam kasus WCF, saya ingin memverifikasi bahwa data yang ditempatkan pada properti sudah benar dan harus diverifikasi menggunakan enkripsi / dekripsi. Fungsi yang saya gunakan untuk dekripsi kebetulan menggunakan fungsi async dari vendor pihak ketiga. (TIDAK BANYAK SAYA BISA MELAKUKAN DI SINI).
RashadRivera

Jawaban:

211

Tidak ada alasan teknis bahwa asyncproperti tidak diizinkan dalam C #. Itu adalah keputusan desain yang disengaja, karena "sifat asinkron" adalah sebuah oxymoron.

Properti harus mengembalikan nilai saat ini; mereka seharusnya tidak memulai operasi latar belakang.

Biasanya, ketika seseorang menginginkan "properti asinkron", yang sebenarnya mereka inginkan adalah salah satunya:

  1. Metode asinkron yang mengembalikan nilai. Dalam hal ini, ubah properti menjadi asyncmetode.
  2. Nilai yang dapat digunakan dalam pengikatan data tetapi harus dihitung / diambil secara tidak sinkron. Dalam hal ini, gunakan asyncmetode pabrik untuk objek yang berisi atau gunakan async InitAsync()metode. Nilai yang terikat data akan default(T)sampai nilai dihitung / diambil.
  3. Nilai yang mahal untuk dibuat, tetapi harus di-cache untuk digunakan di masa mendatang. Dalam hal ini, gunakan AsyncLazy dari blog saya atau perpustakaan AsyncEx . Ini akan memberi Anda awaitproperti yang bisa.

Pembaruan: Saya membahas properti asinkron di salah satu posting blog "async OOP" terbaru saya.

Stephen Cleary
sumber
Di titik 2. IMHO Anda tidak memperhitungkan skenario biasa di mana pengaturan properti harus lagi menginisialisasi data yang mendasarinya (tidak hanya dalam constructor). Apakah ada cara lain selain menggunakan Nito AsyncEx atau menggunakan Dispatcher.CurrentDispatcher.Invoke(new Action(..)?
Gerard
@ Gerard: Saya tidak mengerti mengapa poin (2) tidak berfungsi dalam kasus itu. Cukup terapkan INotifyPropertyChanged, lalu putuskan apakah Anda ingin nilai lama dikembalikan atau default(T)saat pembaruan asinkron sedang dalam penerbangan.
Stephen Cleary
1
@Stephan: ok, tapi ketika saya memanggil metode async di setter, saya mendapatkan peringatan CS4014 "tidak dinanti" (atau hanya di Framework 4.0?). Apakah Anda menyarankan untuk menekan peringatan itu dalam kasus seperti itu?
Gerard
@ Gerard: Rekomendasi pertama saya adalah menggunakan NotifyTaskCompletiondari proyek AsyncEx saya . Atau Anda bisa membangun sendiri; tidak sesulit itu.
Stephen Cleary
1
@Stephan: ok akan mencobanya. Mungkin artikel yang bagus tentang skenario-view-model-sinkronisasi asinkron ini ada di tempat. Misalnya mengikat {Binding PropName.Result}tidak sepele bagi saya untuk mencari tahu.
Gerard
101

Anda tidak dapat menyebutnya asinkron, karena tidak ada dukungan properti asinkron, hanya metode asinkron. Dengan demikian, ada dua opsi, keduanya mengambil keuntungan dari fakta bahwa metode asinkron dalam CTP benar-benar hanya metode yang mengembalikan Task<T>atau Task:

// Make the property return a Task<T>
public Task<IEnumerable> MyList
{
    get
    {
         // Just call the method
         return MyAsyncMethod();
    }
}

Atau:

// Make the property blocking
public IEnumerable MyList
{
    get
    {
         // Block via .Result
         return MyAsyncMethod().Result;
    }
}
Reed Copsey
sumber
1
Terima kasih atas tanggapan Anda. Opsi A: Mengembalikan Tugas tidak benar-benar berfungsi untuk tujuan yang mengikat. Opsi B: .Hasil, seperti yang Anda sebutkan, memblokir utas UI (dalam Silverlight), oleh karena itu diperlukan operasi untuk mengeksekusi pada utas latar belakang. Saya akan melihat apakah saya dapat menemukan solusi yang bisa diterapkan dengan ide ini.
Doguhan Uluca
3
@ Duluca: Anda juga dapat mencoba memiliki metode yang seperti private async void SetupList() { MyList = await MyAsyncMethod(); } ini akan menyebabkan MyList diatur (dan kemudian secara otomatis mengikat, jika mengimplementasikan INPC) segera setelah operasi async selesai ...
Reed Copsey
Properti harus dalam objek yang saya nyatakan sebagai sumber halaman, jadi saya benar-benar membutuhkan panggilan ini berasal dari pengambil. Silakan lihat jawaban saya untuk solusi yang saya buat.
Doguhan Uluca
1
@ Duluca: Itu, secara efektif, apa yang saya sarankan Anda lakukan ... Sadarilah, meskipun, ini jika Anda mengakses Judul beberapa kali dengan cepat, solusi Anda saat ini akan mengarah ke beberapa panggilan secara getTitle()bersamaan ...
Reed Copsey
Poin yang sangat bagus. Meskipun bukan masalah untuk kasus spesifik saya, pemeriksaan boolean untuk isLoading akan memperbaiki masalah ini.
Doguhan Uluca
55

Saya benar-benar membutuhkan panggilan untuk berasal dari metode get, karena arsitektur dipisahkan saya. Jadi saya datang dengan implementasi berikut.

Penggunaan: Judul dalam ViewModel atau objek yang bisa Anda deklarasikan secara statis sebagai sumber halaman. Ikat padanya dan nilainya akan dipopulasi tanpa memblokir UI, ketika getTitle () kembali.

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");
        }
    }
}
Doguhan Uluca
sumber
9
updfrom 18/07/2012 dalam Win8 RP kita harus mengubah panggilan Dispatcher ke: Window.Current.CoreWindow.Dispatcher.RunAsync (CoreDispatcherPriority.Normal, async () => {Judul = menunggu GetTytleAsync (url);});
Anton Sizikov
7
@ChristopherStevenson, saya juga berpikir begitu, tapi saya tidak percaya itu masalahnya. Karena pengambil sedang dieksekusi sebagai api dan lupa, tanpa memanggil setter setelah selesai mengikat tidak akan diperbarui ketika pengambil telah selesai excuting.
Iain
3
Tidak, ini memiliki dan memiliki kondisi balapan, tetapi pengguna tidak akan melihatnya karena 'RaisePropertyChanged ("Judul")'. Itu kembali sebelum selesai. Tapi, setelah selesai Anda mengatur properti. Itu kebakaran acara PropertyChanged. Binder mendapat nilai properti lagi.
Medeni Baykal
1
Pada dasarnya, pengambil pertama akan mengembalikan nilai nol, maka akan diperbarui. Perhatikan bahwa jika kita ingin getTitle dipanggil setiap waktu, mungkin ada loop buruk.
tofutim
1
Anda juga harus menyadari bahwa pengecualian apa pun dalam panggilan async itu akan sepenuhnya ditelan. Mereka bahkan tidak menjangkau penangan pengecualian tidak tertangani Anda pada aplikasi jika Anda memilikinya.
Philter
9

Saya pikir kita bisa menunggu nilainya hanya mengembalikan nol pertama dan kemudian mendapatkan nilai nyata, jadi dalam kasus Pure MVVM (proyek PCL misalnya) saya pikir yang berikut ini adalah solusi paling elegan:

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();
}
Juan Pablo Garcia Coello
sumber
3
Bukankah ini menghasilkan peringatan kompilerCS4014: Async method invocation without an await expression
Nick
6
Jadilah sangat skeptis mengikuti saran ini. Tonton video ini kemudian buat pikiran Anda sendiri: channel9.msdn.com/Series/Three-Essential-Tips-for-Async/… .
Contango
1
Anda HARUS menghindari penggunaan metode "async void"!
SuperJMN
1
setiap teriakan harus datang dengan jawaban yang bijak, maukah Anda @SuperJMN menjelaskan mengapa?
Juan Pablo Garcia Coello
1
@Contango Video yang bagus. Dia berkata, "Gunakan async voidhanya untuk penangan tingkat atas dan sejenisnya". Saya pikir ini mungkin memenuhi syarat sebagai "dan sejenisnya".
HappyNomad
7

Anda bisa menggunakan Taskseperti ini:

public int SelectedTab
        {
            get => selected_tab;
            set
            {
                selected_tab = value;

                new Task(async () =>
                {
                    await newTab.ScaleTo(0.8);
                }).Start();
            }
        }
MohsenB
sumber
5

Saya pikir .GetAwaiter (). GetResult () adalah solusi tepat untuk masalah ini, bukan? misalnya:

string _Title;
public string Title
{
    get
    {
        if (_Title == null)
        {   
            _Title = getTitle().GetAwaiter().GetResult();
        }
        return _Title;
    }
    set
    {
        if (value != _Title)
        {
            _Title = value;
            RaisePropertyChanged("Title");
        }
    }
}
bc3tech
sumber
5
Itu sama dengan hanya memblokir dengan .Result- itu bukan asinkron dan dapat mengakibatkan kebuntuan.
McGuireV10
Anda perlu menambahkan IsAsync = True
Alexsandr Ter
Saya menghargai umpan balik atas jawaban saya; Saya benar-benar ingin seseorang memberikan contoh di mana kebuntuan ini sehingga saya bisa melihatnya beraksi
bc3tech
2

Karena "properti async" Anda ada dalam model tampilan, Anda bisa menggunakan AsyncMVVM :

class MyViewModel : AsyncBindableBase
{
    public string Title
    {
        get
        {
            return Property.Get(GetTitleAsync);
        }
    }

    private async Task<string> GetTitleAsync()
    {
        //...
    }
}

Ini akan menangani konteks sinkronisasi dan pemberitahuan perubahan properti untuk Anda.

Dmitry Shechtman
sumber
Menjadi properti, harus demikian.
Dmitry Shechtman
Maaf, tapi mungkin saya melewatkan poin kode ini kalau begitu. Bisakah Anda menjelaskan lebih lanjut?
Patrick Hofman
Properti memblokir menurut definisi. GetTitleAsync () berfungsi sebagai "pengambil getar async" tanpa gula sintaksis.
Dmitry Shechtman
1
@DmitryShechtman: Tidak, itu tidak harus diblokir. Inilah yang dimaksudkan untuk mengubah pemberitahuan dan mesin negara. Dan mereka tidak memblokir menurut definisi. Mereka sinkron dengan definisi. Ini tidak sama dengan memblokir. "Memblokir" berarti mereka dapat melakukan pekerjaan berat dan mungkin menghabiskan banyak waktu untuk mengeksekusi. Pada gilirannya, ini adalah properti yang TIDAK HARUS DILAKUKAN.
quetzalcoatl
1

Necromancing.
Di .NET Core / NetStandard2, Anda bisa menggunakan Nito.AsyncEx.AsyncContext.Runalih-alih 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;

            // /programming/6602244/how-to-call-an-async-method-from-a-getter-or-setter
            // /programming/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"));
    }


}

Jika Anda hanya memilih System.Threading.Tasks.Task.Runatau System.Threading.Tasks.Task<int>.Run, maka itu tidak akan berhasil.

Stefan Steiger
sumber
-1

Saya pikir contoh saya di bawah ini mungkin mengikuti pendekatan @ Stephen-Cleary tetapi saya ingin memberikan contoh kode. Ini untuk digunakan dalam konteks pengikatan data misalnya Xamarin.

Konstruktor kelas - atau memang setter properti lain di mana ia bergantung - dapat menyebut kekosongan async yang akan mengisi properti pada penyelesaian tugas tanpa perlu menunggu atau memblokir. Ketika akhirnya mendapatkan nilai, itu akan memperbarui UI Anda melalui mekanisme NotifyPropertyChanged.

Saya tidak yakin tentang efek samping dari memanggil aysnc batal dari konstruktor. Mungkin komentator akan menguraikan penanganan kesalahan dll.

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();
    }


}
Craig.C
sumber
-1

Ketika saya mengalami masalah ini, mencoba menjalankan sinkronisasi metode async baik dari setter atau konstruktor membuat saya menemui jalan buntu pada utas UI, dan menggunakan event handler memerlukan terlalu banyak perubahan dalam desain umum.
Solusinya adalah, seperti sering, hanya menulis secara eksplisit apa yang saya inginkan terjadi secara implisit, yaitu memiliki utas lain menangani operasi dan mendapatkan utas utama untuk menunggu sampai selesai:

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

Anda bisa membantah bahwa saya menyalahgunakan kerangka kerja, tetapi itu berhasil.

Yuval Perelman
sumber
-1

Saya meninjau semua jawaban tetapi semua memiliki masalah kinerja.

misalnya di:

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 () => {Judul = menunggu getTitle ();});

gunakan operator yang bukan jawaban yang baik.

tetapi ada solusi sederhana, lakukan saja:

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");
            }
        }
    }
Mahdi Rastegari
sumber
jika fungsi Anda adalah asyn, gunakan getTitle (). wait () alih-alih getTitle ()
Mahdi Rastegari
-4

Anda dapat mengubah proerty menjadi Task<IEnumerable>

dan lakukan sesuatu seperti:

get
{
    Task<IEnumerable>.Run(async()=>{
       return await getMyList();
    });
}

dan gunakan seperti menunggu MyList;

Alejandro Guardiola
sumber