Di sini kita memiliki Grid
dengan Button
. Ketika pengguna mengklik tombol, metode dalam kelas Utility dijalankan yang memaksa aplikasi untuk menerima klik pada Grid. Alur kode harus berhenti di sini dan tidak berlanjut sampai pengguna mengklik Grid
.
Saya punya pertanyaan serupa sebelumnya di sini:
Tunggu hingga pengguna mengklik C # WPF
Dalam pertanyaan itu, saya mendapat jawaban menggunakan async / menunggu yang berfungsi, tetapi karena saya akan menggunakannya sebagai bagian dari API, saya tidak ingin menggunakan async / menunggu, karena konsumen kemudian harus menandai metode mereka dengan async yang tidak saya inginkan.
Bagaimana Utility.PickPoint(Grid grid)
cara menulis metode untuk mencapai tujuan ini?
Saya melihat ini yang mungkin membantu tetapi tidak sepenuhnya memahaminya untuk berlaku di sini jujur:
Memblokir sampai suatu acara selesai
Anggap itu sebagai sesuatu seperti metode Console.ReadKey () dalam aplikasi Konsol. Ketika kita memanggil metode ini, aliran kode berhenti sampai kita memasukkan beberapa nilai. Debugger tidak melanjutkan sampai kita memasukkan sesuatu. Saya ingin perilaku yang tepat untuk metode PickPoint (). Aliran kode akan berhenti sampai pengguna mengklik Grid.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid x:Name="View" Background="Green"/>
<Button Grid.Row="1" Content="Pick" Click="ButtonBase_OnClick"/>
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
// do not continue the code flow until the user has clicked on the grid.
// so when we debug, the code flow will literally stop here.
var point = Utility.PickPoint(View);
MessageBox.Show(point.ToString());
}
}
public static class Utility
{
public static Point PickPoint(Grid grid)
{
}
}
sumber
Aync/Await
bagaimana dengan melakukan Operasi A dan menyimpan operasi itu NEGARA sekarang Anda ingin pengguna itu harus mengklik Kotak .. jadi jika pengguna Klik Kotak Anda memeriksa keadaan jika benar maka lakukan operasi Anda yang lain lakukan saja apa pun yang Anda mau ??AutoResetEvent
bukan apa yang kamu inginkan?var point = Utility.PickPoint(Grid grid);
dalam metode Grid Click? melakukan beberapa operasi dan mengembalikan respons?Jawaban:
Pendekatan Anda salah. Didorong oleh peristiwa bukan berarti memblokir dan menunggu acara. Anda tidak pernah menunggu, setidaknya Anda selalu berusaha keras untuk menghindarinya. Menunggu adalah pemborosan sumber daya, memblokir utas dan mungkin memperkenalkan risiko kebuntuan atau utas zombie (seandainya sinyal pelepasan tidak pernah dinaikkan).
Harus jelas bahwa memblokir utas untuk menunggu suatu acara adalah anti-pola karena bertentangan dengan ide suatu acara.
Anda biasanya memiliki dua opsi (modern): mengimplementasikan API asinkron atau API yang dikendalikan oleh peristiwa. Karena Anda tidak ingin mengimplementasikan API Anda tidak sinkron, Anda dibiarkan menggunakan API yang dikendalikan oleh peristiwa.
Kunci dari API yang digerakkan oleh peristiwa adalah, bahwa alih-alih memaksa pemanggil untuk secara bersamaan menunggu hasil atau polling untuk suatu hasil, Anda membiarkan pemanggil melanjutkan dan mengirimnya pemberitahuan, setelah hasilnya siap atau operasi telah selesai. Sementara itu, penelepon dapat terus menjalankan operasi lain.
Ketika melihat masalah dari perspektif threading, maka API yang digerakkan oleh peristiwa memungkinkan utas panggilan misalnya, utas UI, yang mengeksekusi pengendali acara tombol, bebas untuk terus menangani mis. Operasi terkait UI lainnya, seperti merender elemen UI atau menangani input pengguna seperti gerakan mouse dan penekanan tombol. API yang digerakkan oleh peristiwa memiliki efek atau tujuan yang sama seperti API asinkron, meskipun jauh lebih tidak nyaman.
Karena Anda tidak memberikan rincian yang cukup tentang apa yang sebenarnya Anda coba lakukan, apa
Utility.PickPoint()
yang sebenarnya dilakukan dan apa hasil dari tugas tersebut atau mengapa pengguna harus mengklik `Grid, saya tidak bisa menawarkan solusi yang lebih baik kepada Anda. . Saya hanya dapat menawarkan pola umum tentang bagaimana menerapkan kebutuhan Anda.Aliran atau sasaran Anda jelas dibagi menjadi setidaknya dua langkah untuk menjadikannya urutan operasi:
Grid
dengan setidaknya dua kendala:
Ini memerlukan dua pemberitahuan untuk klien API untuk memungkinkan interaksi yang tidak menghalangi:
Anda harus membiarkan API Anda menerapkan perilaku dan kendala ini dengan memaparkan dua metode publik dan dua acara publik.
Terapkan / refactor Utility API
Utility.cs
PickPointCompletedEventArgs.cs
Gunakan API
MainWindow.xaml.cs
MainWindow.xaml
Catatan
Acara yang diangkat pada utas latar akan mengeksekusi penangannya pada utas yang sama. Mengakses
DispatcherObject
elemen UI seperti dari handler, yang dieksekusi pada utas latar belakang, membutuhkan operasi kritis yang harus dilakukan untukDispatcher
menggunakan salah satuDispatcher.Invoke
atauDispatcher.InvokeAsync
untuk menghindari pengecualian lintas-benang.Baca komentar tentang
DispatcherObject
untuk mempelajari lebih lanjut tentang fenomena ini yang disebut afinitas dispatcher atau afinitas utas.Beberapa pemikiran - balas komentar Anda
Karena Anda mendekati saya untuk menemukan solusi pemblokiran yang "lebih baik", memberi saya contoh aplikasi konsol, saya merasa meyakinkan Anda, bahwa persepsi atau sudut pandang Anda benar-benar salah.
Aplikasi konsol adalah sesuatu yang sangat berbeda. Konsep threading sedikit berbeda. Aplikasi konsol tidak memiliki GUI. Input / output / error stream saja. Anda tidak dapat membandingkan arsitektur aplikasi konsol dengan aplikasi GUI yang kaya. Ini tidak akan berhasil. Anda benar-benar harus memahami dan menerima ini.
Juga jangan tertipu oleh penampilan . Apakah Anda tahu apa yang terjadi di dalam
Console.ReadLine
? Bagaimana ini diterapkan ? Apakah ia memblokir utas utama dan secara paralel ia membaca input? Atau hanya polling?Berikut ini adalah implementasi asli dari
Console.ReadLine
:Seperti yang Anda lihat, ini adalah operasi sinkron sederhana . Ini polling untuk input pengguna dalam loop "tak terbatas". Tidak ada blok sihir dan lanjutkan.
WPF dibuat dengan thread rendering dan UI. Utas-utas itu selalu berputar untuk berkomunikasi dengan OS seperti menangani input pengguna - menjaga aplikasi tetap responsif . Anda tidak pernah ingin menjeda / memblokir utas ini karena ini akan menghentikan kerangka kerja dari pekerjaan latar belakang yang penting, seperti menanggapi peristiwa mouse - Anda tidak ingin mouse membeku:
waiting = thread blocking = unresponsiveness = bad UX = pengguna / pelanggan yang kesal = masalah di kantor.
Terkadang, alur aplikasi mengharuskan untuk menunggu input atau rutin untuk menyelesaikan. Tetapi kami tidak ingin memblokir utas utama.
Itu sebabnya orang menemukan model pemrograman asinkron yang kompleks, untuk memungkinkan menunggu tanpa memblokir utas utama dan tanpa memaksa pengembang untuk menulis kode multithreading yang rumit dan keliru.
Setiap kerangka kerja aplikasi modern menawarkan operasi asinkron atau model pemrograman asinkron, untuk memungkinkan pengembangan kode sederhana dan efisien.
Fakta bahwa Anda berusaha keras untuk menolak model pemrograman asinkron, menunjukkan beberapa kurangnya pemahaman kepada saya. Setiap pengembang modern lebih suka API yang tidak sinkron daripada yang sinkron. Tidak ada pengembang serius yang peduli untuk menggunakan
await
kata kunci atau menyatakan metodenyaasync
. Tak seorangpun. Anda yang pertama kali saya temui yang mengeluh tentang API tidak sinkron dan yang merasa tidak nyaman untuk digunakan.Jika saya akan memeriksa kerangka kerja Anda, yang menargetkan untuk menyelesaikan masalah terkait UI atau membuat tugas terkait UI lebih mudah, saya akan mengharapkannya tidak sinkron - semuanya.
API terkait UI yang tidak sinkron adalah pemborosan, karena akan mempersulit gaya pemrograman saya, karena itu kode saya yang karenanya menjadi lebih rentan kesalahan dan sulit untuk dipelihara.
Perspektif yang berbeda: ketika Anda mengakui bahwa menunggu memblokir utas UI, sedang menciptakan pengalaman pengguna yang sangat buruk dan tidak diinginkan karena UI akan membeku hingga menunggu, setelah Anda menyadari hal ini, mengapa Anda menawarkan model API atau plugin yang mendorong pengembang untuk melakukan hal ini - menerapkan menunggu?
Anda tidak tahu apa yang akan dilakukan plugin pihak ke-3 dan berapa lama rutinitas akan selesai sampai selesai. Ini hanyalah desain API yang buruk. Ketika API Anda beroperasi pada utas UI maka penelepon API Anda harus dapat membuat panggilan yang tidak memblokirnya.
Jika Anda menolak satu-satunya solusi murah atau anggun, maka gunakan pendekatan event-driven seperti yang ditunjukkan dalam contoh saya.
Itu melakukan apa yang Anda inginkan: mulai rutin - menunggu input pengguna - melanjutkan eksekusi - mencapai tujuan.
Saya benar-benar mencoba beberapa kali untuk menjelaskan mengapa menunggu / memblokir adalah desain aplikasi yang buruk. Sekali lagi, Anda tidak dapat membandingkan UI konsol dengan UI grafis yang kaya, di mana misal penanganan input saja jauh lebih kompleks daripada hanya mendengarkan aliran input. Saya benar-benar tidak tahu tingkat pengalaman Anda dan di mana Anda memulai, tetapi Anda harus mulai merangkul model pemrograman asinkron. Saya tidak tahu alasan mengapa Anda mencoba menghindarinya. Tapi itu sama sekali tidak bijaksana.
Hari ini model pemrograman asinkron diterapkan di mana-mana, di setiap platform, kompiler, setiap lingkungan, browser, server, desktop, database - di mana-mana. Model event-driven memungkinkan untuk mencapai tujuan yang sama, tetapi kurang nyaman untuk digunakan (berlangganan / berhenti berlangganan ke / dari acara, baca dokumen (ketika ada dokumen) untuk mempelajari tentang acara), bergantung pada utas latar belakang. Event-driven kuno dan seharusnya hanya digunakan ketika pustaka asinkron tidak tersedia atau tidak berlaku.
Sebagai catatan tambahan: .NET Framwork (.NET Standard) menawarkan
TaskCompletionSource
(di antara tujuan lain) untuk menyediakan cara sederhana untuk mengonversi API yang digerakkan genap yang ada menjadi API asinkron.Perilaku (apa yang Anda alami atau amati) jauh berbeda dari bagaimana pengalaman ini diterapkan. Dua hal berbeda. Autodesk Anda sangat mungkin menggunakan pustaka asinkron atau fitur bahasa atau mekanisme threading lainnya. Dan itu juga terkait konteks. Ketika metode yang ada di pikiran Anda dieksekusi pada utas latar belakang maka pengembang dapat memilih untuk memblokir utas ini. Dia punya alasan yang sangat bagus untuk melakukan ini atau hanya membuat pilihan desain yang buruk. Anda benar-benar di jalur yang salah;) Memblokir tidak baik.
(Apakah kode sumber Autodesk open source? Atau bagaimana Anda tahu cara penerapannya?)
Aku tidak ingin menyinggung perasaanmu, percayalah padaku. Tapi tolong pertimbangkan kembali untuk mengimplementasikan API Anda tidak sinkron. Hanya di kepala Anda bahwa pengembang tidak suka menggunakan async / menunggu. Anda jelas salah pola pikir. Dan lupakan argumen aplikasi konsol - tidak masuk akal;)
API terkait UI HARUS menggunakan async / menunggu kapan pun memungkinkan. Jika tidak, Anda meninggalkan semua pekerjaan untuk menulis kode non-pemblokiran ke klien API Anda. Anda akan memaksa saya untuk membungkus setiap panggilan ke API Anda ke utas latar belakang. Atau menggunakan penanganan acara yang kurang nyaman. Percayalah - setiap pengembang suka mendekorasi anggotanya
async
, daripada melakukan penanganan acara. Setiap kali Anda menggunakan acara, Anda mungkin berisiko kebocoran memori potensial - tergantung pada beberapa keadaan, tetapi risikonya nyata dan tidak jarang saat pemrograman ceroboh.Saya sangat berharap Anda mengerti mengapa memblokir itu buruk. Saya sangat berharap Anda memutuskan untuk menggunakan async / menunggu untuk menulis API asinkron modern. Meskipun demikian, saya menunjukkan kepada Anda cara yang sangat umum untuk menunggu non-blocking, menggunakan acara, meskipun saya mendorong Anda untuk menggunakan async / menunggu.
Jika Anda tidak ingin mengizinkan plugin memiliki akses langsung ke elemen UI, Anda harus menyediakan antarmuka untuk mendelegasikan acara atau mengekspos komponen internal melalui objek yang diabstraksi.
API secara internal akan berlangganan acara UI atas nama Add-in dan kemudian mendelegasikan acara tersebut dengan memaparkan acara "pembungkus" yang sesuai untuk klien API. API Anda harus menawarkan beberapa kaitan tempat Add-in dapat terhubung untuk mengakses komponen aplikasi tertentu. Plugin API bertindak seperti adaptor atau fasad untuk memberikan akses eksternal ke internal.
Untuk memungkinkan tingkat isolasi.
Lihatlah bagaimana Visual Studio mengelola plugin atau memungkinkan kita untuk mengimplementasikannya. Berpura-puralah Anda ingin menulis plugin untuk Visual Studio dan melakukan riset tentang cara melakukan ini. Anda akan menyadari bahwa Visual Studio memaparkan internal melalui antarmuka atau API. EG Anda dapat memanipulasi editor kode atau mendapatkan informasi tentang konten editor tanpa akses nyata ke sana.
sumber
var str = Console.ReadLine(); Console.WriteLine(str);
Apa yang terjadi ketika Anda menjalankan aplikasi dalam mode debug. Ini akan berhenti di baris kode pertama dan memaksa Anda untuk memasukkan nilai di UI Konsol dan kemudian setelah Anda memasukkan sesuatu dan tekan Enter, itu akan mengeksekusi baris berikutnya dan benar-benar mencetak apa yang Anda masukkan. Saya berpikir tentang perilaku yang persis sama tetapi dalam aplikasi WPF.Saya pribadi berpikir ini terlalu rumit oleh semua orang, tetapi mungkin saya tidak sepenuhnya memahami alasan mengapa ini perlu dilakukan dengan cara tertentu, tetapi sepertinya bool check sederhana dapat digunakan di sini.
Pertama dan yang terpenting, buat kisi-kisi Anda dapat diuji dengan menyetel
Background
danIsHitTestVisible
properti, atau bahkan tidak akan menangkap klik mouse.Selanjutnya buat nilai bool yang dapat menyimpan apakah acara "GridClick" harus terjadi. Ketika kisi diklik, periksa nilai itu dan pelaksanaan eksekusi dari kisi klik kisi jika sedang menunggu klik.
Contoh:
sumber
Saya mencoba beberapa hal tetapi saya tidak dapat membuatnya tanpa
async/await
. Karena jika kita tidak menggunakannya itu menyebabkanDeadLock
atau UI diblokir dan kemudian kita dapat mengambilGrid_Click
input.sumber
Anda dapat memblokir secara tidak sinkron menggunakan
SemaphoreSlim
:Anda tidak bisa, dan Anda juga tidak ingin, memblokir utas pengirim secara serempak karena ia tidak akan pernah bisa menangani klik pada
Grid
, yaitu tidak dapat diblokir dan menangani acara secara bersamaan.sumber
Console.Readline
blok , artinya tidak kembali sampai baris telah dibaca. AndaPickPoint
metode tidak. Segera kembali. Ini bisa berpotensi memblokir tetapi Anda tidak akan dapat menangani input UI sementara itu seperti yang saya tulis dalam jawaban saya. Dengan kata lain, Anda harus menangani klik di dalam metode untuk mendapatkan perilaku yang sama.PickPoint
yang menangani acara mouse. Saya gagal melihat ke mana Anda akan pergi dengan ini?Secara teknis dimungkinkan dengan
AutoResetEvent
dan tanpaasync/await
, tetapi ada kelemahan yang signifikan:Kekurangannya: Jika Anda memanggil metode ini secara langsung dalam penangan peristiwa tombol seperti kode sampel Anda, kebuntuan akan terjadi dan Anda akan melihat bahwa aplikasi berhenti merespons. Karena Anda menggunakan satu-satunya utas UI untuk menunggu klik pengguna, itu tidak dapat menanggapi tindakan pengguna apa pun, termasuk klik pengguna pada kisi.
Konsumen metode ini harus memintanya di utas lain untuk mencegah kebuntuan. Jika bisa dijamin, tidak apa-apa. Jika tidak, Anda perlu menjalankan metode seperti ini:
Ini dapat menyebabkan lebih banyak masalah bagi konsumen API Anda kecuali mereka terbiasa mengelola utas mereka sendiri. Itu sebabnya
async/await
ditemukan.sumber
Saya pikir masalahnya ada pada desain itu sendiri. Jika API Anda bekerja pada elemen tertentu maka itu harus digunakan dalam event handler dari elemen ini, bukan pada elemen lain.
Misalnya, di sini kami ingin mendapatkan posisi acara klik pada Grid, API harus digunakan dalam pengendali event yang terkait dengan acara pada elemen Grid, bukan pada elemen tombol.
Sekarang, jika persyaratannya adalah menangani klik pada Grid hanya setelah kita mengklik Button, maka tanggung jawab Button adalah menambahkan event handler pada Grid, dan event klik pada Grid akan menampilkan kotak pesan dan menghapus pengendali acara ini ditambahkan oleh tombol sehingga tidak akan memicu lagi setelah klik ini ... (tidak perlu memblokir Utas UI)
Hanya untuk mengatakan bahwa jika Anda memblokir utas UI pada klik tombol, saya tidak berpikir utas UI akan dapat memicu acara klik pada Grid sesudahnya.
sumber
Pertama-tama, utas UI tidak dapat diblokir seperti jawaban yang Anda dapatkan dari pertanyaan awal Anda.
Jika Anda bisa setuju dengan itu, maka menghindari async / menunggu untuk membuat pelanggan Anda melakukan sedikit modifikasi bisa dilakukan, dan bahkan tidak memerlukan multi-threading.
Tetapi jika Anda ingin memblokir utas UI atau "aliran kode", jawabannya tidak mungkin. Karena jika utas UI diblokir, tidak ada input lebih lanjut yang dapat diterima.
Karena Anda semacam menyebutkan tentang aplikasi konsol, saya hanya melakukan beberapa penjelasan sederhana.
Ketika Anda menjalankan aplikasi konsol atau panggilan
AllocConsole
dari proses yang tidak melampirkan ke konsol (jendela), conhost.exe yang dapat menyediakan konsol (jendela) akan dieksekusi dan proses aplikasi konsol atau pemanggil akan dilampirkan ke konsol ( jendela).Jadi kode apa pun yang Anda tulis yang dapat memblokir utas penelepon seperti
Console.ReadKey
tidak akan memblokir utas UI jendela konsol, apa pun alasannya mengapa saat aplikasi konsol menunggu input Anda tetapi masih dapat menanggapi input lain seperti mengklik mouse.sumber