Di aplikasi metro C # / XAML saya, ada tombol yang memulai proses yang sudah berjalan lama. Jadi, seperti yang disarankan, saya menggunakan async / menunggu untuk memastikan utas UI tidak diblokir:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
await GetResults();
}
private async Task GetResults()
{
// Do lot of complex stuff that takes a long time
// (e.g. contact some web services)
...
}
Kadang-kadang, hal-hal yang terjadi di dalam GetResults akan membutuhkan input pengguna tambahan sebelum dapat melanjutkan. Untuk kesederhanaan, katakanlah pengguna hanya perlu mengklik tombol "lanjutkan".
Pertanyaan saya adalah: bagaimana saya bisa menunda eksekusi GetResults sedemikian rupa sehingga menunggu acara seperti klik tombol lain?
Inilah cara yang jelek untuk mencapai apa yang saya cari: pengendali acara untuk tombol "lanjut menetapkan tanda ...
private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
_continue = true;
}
... dan GetResults melakukan polling secara berkala:
buttonContinue.Visibility = Visibility.Visible;
while (!_continue) await Task.Delay(100); // poll _continue every 100ms
buttonContinue.Visibility = Visibility.Collapsed;
Pemungutan suara jelas mengerikan (menunggu sibuk / buang-buang siklus) dan saya sedang mencari sesuatu yang berbasis acara.
Ada ide?
Btw dalam contoh sederhana ini, satu solusi tentu saja untuk membagi GetResult () menjadi dua bagian, memanggil bagian pertama dari tombol mulai dan bagian kedua dari tombol melanjutkan. Pada kenyataannya, hal-hal yang terjadi di GetResults lebih kompleks dan berbagai jenis input pengguna dapat diperlukan pada titik yang berbeda dalam eksekusi. Jadi memecah logika menjadi beberapa metode akan menjadi non-sepele.
ManualResetEvent(Slim)
sepertinya tidak mendukungWaitAsync()
.async
tidak berarti "berjalan di utas yang berbeda", atau sesuatu seperti itu. Itu hanya berarti "Anda dapat menggunakanawait
metode ini". Dan dalam hal ini, memblokir di dalamGetResults()
sebenarnya akan memblokir utas UI.await
dengan sendirinya tidak menjamin bahwa utas lainnya dibuat, tetapi hal itu menyebabkan semua yang lain setelah pernyataan berjalan sebagai kelanjutan dariTask
atau menunggu yang Anda panggilawait
. Lebih sering daripada tidak, itu adalah beberapa jenis operasi asynchronous, yang bisa IO selesai, atau sesuatu yang merupakan di thread lain.SemaphoreSlim.WaitAsync
tidak hanya mendorongWait
ke thread pool thread.SemaphoreSlim
memiliki antrianTask
s yang tepat yang digunakan untuk mengimplementasikanWaitAsync
.Ketika Anda memiliki hal yang tidak biasa yang Anda perlukan
await
, jawaban yang paling mudah adalah seringTaskCompletionSource
(atauasync
primitif yang diaktifkan berdasarkanTaskCompletionSource
).Dalam hal ini, kebutuhan Anda cukup sederhana, sehingga Anda bisa
TaskCompletionSource
langsung menggunakan :Secara logis,
TaskCompletionSource
sepertiasync
ManualResetEvent
, kecuali bahwa Anda hanya dapat "mengatur" acara satu kali dan acara tersebut dapat memiliki "hasil" (dalam hal ini, kami tidak menggunakannya, jadi kami hanya mengatur hasilnya kenull
).sumber
Berikut adalah kelas utilitas yang saya gunakan:
Dan inilah cara saya menggunakannya:
sumber
new Task(() => { });
akan selesai secara instan?Kelas Pembantu Sederhana:
Pemakaian:
sumber
example.YourEvent
?Idealnya, Anda tidak melakukannya . Meskipun Anda tentu saja dapat memblokir utas async, itu adalah pemborosan sumber daya, dan tidak ideal.
Pertimbangkan contoh kanonik di mana pengguna pergi makan siang sementara tombol sedang menunggu untuk diklik.
Jika Anda telah menghentikan kode asinkron Anda sambil menunggu input dari pengguna, maka itu hanya membuang sumber daya sementara utas itu dijeda.
Yang mengatakan, lebih baik jika dalam operasi asinkron Anda, Anda mengatur status yang perlu Anda pertahankan ke titik di mana tombol diaktifkan dan Anda "menunggu" di klik. Pada titik itu,
GetResults
metode Anda berhenti .Kemudian, ketika tombol tersebut diklik, berdasarkan pada yang Anda telah disimpan, Anda mulai tugas asynchronous lain untuk melanjutkan pekerjaan.
Karena
SynchronizationContext
akan ditangkap dalam event handler yang memanggilGetResults
(kompiler akan melakukan ini sebagai hasil dari menggunakanawait
kata kunci yang digunakan, dan fakta bahwa SynchronizationContext.Current harus non-nol, mengingat Anda berada dalam aplikasi UI), Anda dapat menggunakanasync
/await
seperti itu:ContinueToGetResultsAsync
adalah metode yang terus mendapatkan hasil jika tombol Anda ditekan. Jika tombol Anda tidak ditekan, maka pengendali acara Anda tidak melakukan apa pun.sumber
GetResults
mengembalikan aTask
.await
hanya mengatakan "jalankan tugas, dan ketika tugas selesai, lanjutkan kode setelah ini". Mengingat bahwa ada konteks sinkronisasi, panggilan tersebut dikembalikan ke utas UI, karena ditangkap padaawait
.await
adalah tidak sama denganTask.Wait()
, tidak sedikit.Wait()
. Tetapi kode diGetResults()
akan berjalan pada utas UI di sini, tidak ada utas lainnya. Dengan kata lain, ya,await
pada dasarnya menjalankan tugas, seperti yang Anda katakan, tetapi di sini, tugas itu juga berjalan di utas UI.await
dan kemudian kode setelahnyaawait
, tidak ada pemblokiran. Sisa kode di marshal kembali dalam kelanjutan dan dijadwalkan melaluiSynchronizationContext
.Stephen Toub menerbitkan
AsyncManualResetEvent
kelas ini di blog-nya .sumber
Dengan Ekstensi Reaktif (Rx.Net)
Anda dapat menambahkan Rx dengan Sistem Paket Nuget. Reaktif
Sampel yang diuji:
sumber
Saya menggunakan kelas AsyncEvent saya sendiri untuk acara yang bisa ditunggu.
Untuk mendeklarasikan suatu peristiwa di kelas yang menimbulkan peristiwa:
Untuk mengangkat acara:
Untuk berlangganan acara:
sumber