Bagaimana cara memblokir aliran kode sampai suatu peristiwa dipecat dalam C #


10

Di sini kita memiliki Griddengan 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)
    {

    }
}

Cara yang jelas adalah Aync/Awaitbagaimana 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 ??
Rao Hammas Hussain

@RaoHammasHussain Saya memperbarui pertanyaan saya dengan tautan yang dapat membantu. Metode utilitas akan menjadi bagian dari API yang akan dipanggil oleh pengguna API setiap kali dia ingin meminta pengguna akhir untuk mengklik pada layar. Anggap saja seperti jendela prompt untuk teks dalam aplikasi windows normal atau metode Console.Readline (). Dalam kasus ini, aliran kode berhenti hingga pengguna memasukkan sesuatu. Sekarang saya menginginkan hal yang tepat tetapi kali ini pengguna mengklik pada layar.
Vahid

AutoResetEventbukan apa yang kamu inginkan?
Rao Hammas Hussain

@RaoHammasHussain saya pikir begitu tetapi benar-benar tidak tahu bagaimana menggunakannya di sini.
Vahid

Sepertinya Anda sengaja menerapkan NEGARA TUNGGU. apakah itu benar-benar diperlukan? cuz tidak bisakah kamu memasukkan ini var point = Utility.PickPoint(Grid grid);dalam metode Grid Click? melakukan beberapa operasi dan mengembalikan respons?
Rao Hammas Hussain

Jawaban:


8

"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:

  1. Jalankan operasi 1, ketika pengguna mengklik tombol
  2. Jalankan operasi 2 (lanjutkan / selesaikan operasi 1), ketika pengguna mengklik pada Grid

dengan setidaknya dua kendala:

  1. Opsional: urutan harus diselesaikan sebelum klien API diizinkan untuk mengulanginya. Urutan selesai setelah operasi 2 dijalankan hingga selesai.
  2. Operasi 1 selalu dijalankan sebelum operasi 2. Operasi 1 memulai urutan.
  3. 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:

  1. Operasi 1 selesai (atau diperlukan interaksi)
  2. 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 DispatcherObjectelemen UI seperti dari handler, yang dieksekusi pada utas latar belakang, membutuhkan operasi kritis yang harus dilakukan untuk Dispatchermenggunakan salah satu Dispatcher.Invokeatau Dispatcher.InvokeAsyncuntuk menghindari pengecualian lintas-benang.
Baca komentar tentang DispatcherObjectuntuk 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 awaitkata 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.


Hai, terima kasih telah mendekati pertanyaan dari perspektif lain. Maaf jika pertanyaannya agak kabur pada detail. 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.
Vahid

Dalam aplikasi CAD yang saya kembangkan, para pengguna seharusnya dapat memperpanjangnya dengan addins / plugins. API akan memungkinkan programmer untuk memiliki akses ke UI dan lain-lain. Sekarang misalkan programmer ingin mengembangkan addin bahwa ketika sebuah tombol diklik, pengguna akhir diminta untuk memilih titik di UI dan kemudian kode akan melakukan yang lain hal-hal keren dengan poin yang diberikan. Mungkin mereka akan meminta titik lain untuk diambil dan menggambar garis, dll.
Vahid

Saya telah melihat perilaku yang tepat di Autodesk Revit.
Vahid

1
Ada yang ingin saya katakan tentang kebutuhan Anda. Harap baca jawaban saya yang diperbarui. Saya memposting balasan di sana karena entah bagaimana jadi lebih lama. Saya akui Anda benar-benar telah memicu saya. Tolong, sambil membaca ingatlah bahwa saya tidak ingin menyinggung perasaan Anda.
BionicCode

Terima kasih atas jawaban Anda yang diperbarui. Tentu saja, saya tidak tersinggung. Sebaliknya, saya sangat berterima kasih atas waktu dan upaya yang telah Anda lakukan untuk ini.
Vahid

5

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 Backgrounddan IsHitTestVisibleproperti, atau bahkan tidak akan menangkap klik mouse.

<grid MouseLeftButtonUp="Grid_MouseLeftButtonUp" IsHitTestVisible="True" Background="Transparent">

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:

bool awaitingClick = false;


private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
   awaitingClick=true;
}

private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{     
     //Stop here if the program shouldn't do anything when grid is clicked
     if (!awaitingClick) { return; } 

     //Run event
     var point = Utility.PickPoint(View);
     MessageBox.Show(point.ToString());

     awaitingClick=false;//Reset
}

Hai Tronald, saya pikir Anda telah salah paham pertanyaannya. Yang saya butuhkan adalah kode untuk berhenti di Utility.PickPoint (View) dan melanjutkan hanya setelah pengguna mengklik Grid.
Vahid

Oh ya, saya benar-benar salah paham. Saya minta maaf, saya tidak menyadari Anda perlu segalanya untuk benar-benar berhenti. Saya tidak berpikir itu mungkin tanpa multi-threading karena seluruh UI akan diblokir.
Tronald

Saya masih tidak yakin apakah itu tidak mungkin. Hal ini dimungkinkan dengan async / wait yang bukan merupakan solusi multi-threaded. Tapi yang saya butuhkan adalah alternatif dari solusi async / menunggu.
Vahid

1
Tentu, tetapi Anda menyebutkan bahwa Anda tidak dapat menggunakan async / menunggu. Sepertinya Anda perlu menggunakan dispatcher dan utas yang terpisah dari utas utama (yang dijalankan pada UI). Saya harap Anda menemukan cara lain karena saya tertarik pada diri saya sendiri
Tronald

2

Saya mencoba beberapa hal tetapi saya tidak dapat membuatnya tanpa async/await. Karena jika kita tidak menggunakannya itu menyebabkan DeadLockatau UI diblokir dan kemudian kita dapat mengambil Grid_Clickinput.

private async void ToolBtn_OnClick(object sender, RoutedEventArgs e)
{
    var senderBtn = sender as Button;
    senderBtn.IsEnabled = false;

    var response = await Utility.PickPoint(myGrid);
    MessageBox.Show(response.ToString());
    senderBtn.IsEnabled = true;
}  

public static class Utility
{
    private static TaskCompletionSource<bool> tcs;
    private static Point _point = new Point();

    public static async Task<Point> PickPoint(Grid grid)
    {
        tcs = new TaskCompletionSource<bool>();
        _point = new Point();

        grid.MouseLeftButtonUp += GridOnMouseLeftButtonUp;


        await tcs.Task;

        grid.MouseLeftButtonUp -= GridOnMouseLeftButtonUp;
        return _point;
    }


    private static void GridOnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {

        // do something here ....
        _point = new Point { X = 23, Y = 34 };
        // do something here ....

        tcs.SetResult(true); // as soon its set it will go back

    }
}

@ terima kasih, ini adalah jawaban yang sama yang saya dapatkan untuk pertanyaan saya yang lain yang menggunakan async / tunggu.
Vahid

Oh ya ! Saya menyadarinya sekarang, tapi saya rasa itu satu-satunya cara saya menemukan itu bekerja
Rao Hammas Hussain

2

Anda dapat memblokir secara tidak sinkron menggunakan SemaphoreSlim:

public partial class MainWindow : Window, IDisposable
{
    private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(0, 1);

    public MainWindow()
    {
        InitializeComponent();
    }

    private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        var point = Utility.PickPoint(View);

        // 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.
        await _semaphoreSlim.WaitAsync();

        MessageBox.Show(point.ToString());
    }

    private void View_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        //click on grid detected....
        _semaphoreSlim.Release();
    }

    protected override void OnClosed(EventArgs e)
    {
        base.OnClosed(e);
        Dispose();
    }

    public void Dispose() => _semaphoreSlim.Dispose();
}

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.


Terima kasih atas jawaban alternatif. Saya bertanya-tanya bagaimana ini dilakukan misalnya di Console.Readline ()? Ketika Anda mencapai metode ini di debugger, itu secara ajaib berhenti di sana kecuali kita memasukkan sesuatu? Apakah secara fundamental berbeda dalam aplikasi Konsol? Tidak bisakah kita memiliki perilaku yang sama dalam aplikasi WinForms / WPF? Saya telah melihat ini di Autodesk Revit's API, ada metode PickPoint () yang memaksa Anda untuk memilih titik di layar dan saya belum melihat async / menunggu digunakan! Apakah paling tidak mungkin menyembunyikan kata kunci yang menunggu dan memanggilnya entah bagaimana dari metode sinkronisasi?
Vahid

@Vahid: Console.Readline blok , artinya tidak kembali sampai baris telah dibaca. Anda PickPointmetode 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.
mm8

Console.Realine () memblokir tetapi pada saat yang sama acara KeyPress diizinkan. Tidak bisakah kita memiliki perilaku yang sama persis di sini? Memblokir oleh PickPoint () dan hanya mengizinkan MouseEvents? Saya tidak dapat memahami mengapa dimungkinkan di Konsol tetapi tidak pada aplikasi berbasis UI.
Vahid

Maka Anda perlu mengatur dispatcher terpisah PickPointyang menangani acara mouse. Saya gagal melihat ke mana Anda akan pergi dengan ini?
mm8

1
@Vahind: Buat kode async dan biarkan pengguna menunggu metode itu? Ini adalah API yang saya harapkan sebagai pengembang UI. Memanggil metode pemblokiran dalam aplikasi UI tidak masuk akal.
mm8

2

Secara teknis dimungkinkan dengan AutoResetEventdan tanpa async/await, tetapi ada kelemahan yang signifikan:

public static Point PickPoint(Grid grid)
{
    var pointPicked = new AutoResetEvent(false);
    grid.MouseLeftButtonUp += (s, e) => 
    {
        // do whatever after the grid is clicked

        // signal the end of waiting
        pointPicked.Set();
    };

    // code flow will stop here and wait until the grid is clicked
    pointPicked.WaitOne();
    // return something...
}

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:

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    // here I used ThreadPool, but you may use other means to run on another thread
    ThreadPool.QueueUserWorkItem(new WaitCallback(Capture));
}

private void Capture(object state)
{
    // 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());
}

Ini dapat menyebabkan lebih banyak masalah bagi konsumen API Anda kecuali mereka terbiasa mengelola utas mereka sendiri. Itu sebabnya async/awaitditemukan.


Terima kasih Ken, mungkinkah addin dimulai dari utas lainnya dan kemudian acara-acaranya tidak memblokir utas UI utama?
Vahid

@Vahid Ya dan tidak. Ya, Anda dapat memanggil metode pemblokiran di utas lain dan membungkusnya dengan metode lain. Namun, metode pembungkus masih perlu dipanggil di utas lain selain utas UI untuk menghindari pemblokiran UI. Karena pembungkus akan memblokir utas panggilan jika sinkron . Meskipun secara internal pembungkus memblokir utas lainnya, masih perlu menunggu hasilnya dan memblokir utas panggilan. Jika pemanggil memanggil metode pembungkus di utas UI, UI diblokir.
Ken Hung

0

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.


0

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.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        Utility.PickPoint(View, (x) => MessageBox.Show(x.ToString()));
    }
}

public static class Utility
{
    private static Action<Point> work;

    public static void PickPoint(Grid grid, Action<Point> work)
    {
        if (Utility.work == null)
        {
            grid.PreviewMouseLeftButtonUp += Grid_PreviewMouseLeftButtonUp;
            Utility.work = work;
        }
    }

    private static void Grid_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        var grid = (Grid)sender;
        work.Invoke(e.GetPosition(grid));
        grid.PreviewMouseLeftButtonUp -= Grid_PreviewMouseLeftButtonUp;
        Utility.work = null;
    }
}   

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 AllocConsoledari 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.ReadKeytidak 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.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.