"Bagaimana cara memblokir aliran kode sampai suatu peristiwa dipecat?"
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:
- Jalankan operasi 1, ketika pengguna mengklik tombol
- Jalankan operasi 2 (lanjutkan / selesaikan operasi 1), ketika pengguna mengklik pada
Grid
dengan setidaknya dua kendala:
- Opsional: urutan harus diselesaikan sebelum klien API diizinkan untuk mengulanginya. Urutan selesai setelah operasi 2 dijalankan hingga selesai.
- Operasi 1 selalu dijalankan sebelum operasi 2. Operasi 1 memulai urutan.
- Operasi 1 harus selesai sebelum klien API diizinkan untuk menjalankan operasi 2
Ini memerlukan dua pemberitahuan untuk klien API untuk memungkinkan interaksi yang tidak menghalangi:
- Operasi 1 selesai (atau diperlukan interaksi)
- Operasi 2 (atau tujuan) selesai
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
class Utility
{
public event EventHandler InitializePickPointCompleted;
public event EventHandler<PickPointCompletedEventArgs> PickPointCompleted;
private bool IsPickPointInitialized { get; set; }
private bool IsExecutingSequence { get; set; }
// The prefix 'Begin' signals the caller or client of the API,
// that he also has to end the sequence explicitly
public void BeginPickPoint(param)
{
// Implement constraint 1
if (this.IsExecutingSequence)
{
// Alternatively just return or use Try-do pattern
throw new InvalidOperationException("BeginPickPoint is already executing. Call EndPickPoint before starting another sequence.");
}
// Set the flag that a current sequence is in progress
this.IsExecutingSequence = true;
// Execute operation until caller interaction is required.
// Execute in background thread to allow API caller to proceed with execution.
Task.Run(() => StartOperationNonBlocking(param));
}
public void EndPickPoint(param)
{
// Implement constraint 2 and 3
if (!this.IsPickPointInitialized)
{
// Alternatively just return or use Try-do pattern
throw new InvalidOperationException("BeginPickPoint must have completed execution before calling EndPickPoint.");
}
// Execute operation until caller interaction is required.
// Execute in background thread to allow API caller to proceed with execution.
Task.Run(() => CompleteOperationNonBlocking(param));
}
private void StartOperationNonBlocking(param)
{
... // Do something
// Flag the completion of the first step of the sequence (to guarantee constraint 2)
this.IsPickPointInitialized = true;
// Request caller interaction to kick off EndPickPoint() execution
OnInitializePickPointCompleted();
}
private void CompleteOperationNonBlocking(param)
{
// Execute goal and get the result of the completed task
Point result = ExecuteGoal();
// Reset API sequence
this.IsExecutingSequence = false;
this.IsPickPointInitialized = false;
// Notify caller that execution has completed and the result is available
OnPickPointCompleted(result);
}
private void OnInitializePickPointCompleted()
{
// Set the result of the task
this.InitializePickPointCompleted?.Invoke(this, EventArgs.Empty);
}
private void OnPickPointCompleted(Point result)
{
// Set the result of the task
this.PickPointCompleted?.Invoke(this, new PickPointCompletedEventArgs(result));
}
}
PickPointCompletedEventArgs.cs
class PickPointCompletedEventArgs : EventArgs
{
public Point Result { get; }
public PickPointCompletedEventArgs(Point result)
{
this.Result = result;
}
}
Gunakan API
MainWindow.xaml.cs
partial class MainWindow : Window
{
private Utility Api { get; set; }
public MainWindow()
{
InitializeComponent();
this.Api = new Utility();
}
private void StartPickPoint_OnButtonClick(object sender, RoutedEventArgs e)
{
this.Api.InitializePickPointCompleted += RequestUserInput_OnInitializePickPointCompleted;
// Invoke API and continue to do something until the first step has completed.
// This is possible because the API will execute the operation on a background thread.
this.Api.BeginPickPoint();
}
private void RequestUserInput_OnInitializePickPointCompleted(object sender, EventArgs e)
{
// Cleanup
this.Api.InitializePickPointCompleted -= RequestUserInput_OnInitializePickPointCompleted;
// Communicate to the UI user that you are waiting for him to click on the screen
// e.g. by showing a Popup, dimming the screen or showing a dialog.
// Once the input is received the input event handler will invoke the API to complete the goal
MessageBox.Show("Please click the screen");
}
private void FinishPickPoint_OnGridMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
this.Api.PickPointCompleted += ShowPoint_OnPickPointCompleted;
// Invoke API to complete the goal
// and continue to do something until the last step has completed
this.Api.EndPickPoint();
}
private void ShowPoint_OnPickPointCompleted(object sender, PickPointCompletedEventArgs e)
{
// Cleanup
this.Api.PickPointCompleted -= ShowPoint_OnPickPointCompleted;
// Get the result from the PickPointCompletedEventArgs instance
Point point = e.Result;
// Handle the result
MessageBox.Show(point.ToString());
}
}
MainWindow.xaml
<Window>
<Grid MouseLeftButtonUp="FinishPickPoint_OnGridMouseLeftButtonUp">
<Button Click="StartPickPoint_OnButtonClick" />
</Grid>
</Window>
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 untuk Dispatcher
menggunakan salah satu Dispatcher.Invoke
atau Dispatcher.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.
"Pertimbangkan aplikasi Konsol dengan dua baris kode ini di dalamnya.
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. "
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
:
public virtual String ReadLine()
{
StringBuilder sb = new StringBuilder();
while (true) {
int ch = Read();
if (ch == -1) break;
if (ch == '\r' || ch == '\n')
{
if (ch == '\r' && Peek() == '\n') Read();
return sb.ToString();
}
sb.Append((char)ch);
}
if (sb.Length > 0) return sb.ToString();
return null;
}
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 metodenya async
. 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.
"Saya telah melihat perilaku yang tepat di Autodesk Revit."
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.
"API akan memungkinkan programmer untuk memiliki akses ke UI dan lain-lain. Sekarang misalkan programmer ingin mengembangkan add-in bahwa ketika sebuah tombol diklik, pengguna akhir diminta untuk memilih titik di UI"
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.
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 ??