C # 'da bir olay tetiklenene kadar kod akışını engelleme


11

Burada a Gridile bir var Button. Kullanıcı düğmeyi tıklattığında, uygulamayı bir Grid tıklaması almaya zorlayan Utility sınıfında bir yöntem yürütülür. Kod akışı burada durmalı ve kullanıcı simgesini tıklayana kadar devam etmemelidir Grid.

Burada daha önce benzer bir sorum vardı:

Kullanıcı C # WPF'yi tıklayana kadar bekleyin

Bu soruda, işe yarayan async / await kullanarak bir cevap aldım, ancak bunu bir API'nin parçası olarak kullanacağım için, tüketiciler daha sonra yöntemlerini işaretlemek zorunda kalacakları için async / await kullanmak istemiyorum istemediğim asenkron.

Utility.PickPoint(Grid grid)Bu hedefe ulaşmak için yöntemi nasıl yazarım ?

Ben yardımcı olabilir ama dürüst olmak gerekirse burada uygulamak için tam olarak anlamadı bunu gördüm:

Bir etkinlik tamamlanana kadar engelleme

Konsol uygulamasında Console.ReadKey () yöntemi gibi bir şey olarak düşünün. Bu yöntemi çağırdığımızda, bir değer girene kadar kod akışı durur. Hata ayıklayıcı, bir şey girene kadar devam etmez. PickPoint () yöntemi için tam davranış istiyorum. Kullanıcı Grid'i tıklayana kadar kod akışı duracaktır.

<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)
    {

    }
}

Açık bir şekilde, Aync/AwaitA İşlemi yapma ve bu işlemi STATE kaydetme, kullanıcının Grid'i tıklamasını istersiniz ... kullanıcı Grid'i tıklatırsa, eğer durumu doğru ise, o zaman işleminizi yapın, başka ne yapmak istiyorsanız yapın ??
Rao Hammas Hussain

@RaoHammasHussain Sorumu yardımcı olabilecek bir bağlantıyla güncelledim. Yardımcı program yöntemi, son kullanıcıdan ekrana tıklamasını istediğinde, API kullanıcısının arayacağı bir API'nin parçası olacaktır. Normal Windows uygulamalarında veya Console.Readline () yönteminde metin için bir istem penceresi olarak düşünün. Bu durumlarda, kod akışı kullanıcı bir şey girene kadar durur. Şimdi kesin olanı istiyorum ama bu sefer kullanıcı ekrana tıklıyor.
Vahid

AutoResetEventistediğin şey değil mi?
Rao Hammas Hussain

@RaoHammasHussain Sanırım öyle ama burada nasıl kullanılacağını gerçekten bilmiyorum.
Vahid

Bilerek WAIT STATE uyguluyorsunuz. gerçekten gerekli mi? çünkü bunu sadece var point = Utility.PickPoint(Grid grid);Grid Click yöntemine koyamaz mısın? biraz işlem yapın ve yanıtı iade edin?
Rao Hammas Hussain

Yanıtlar:


9

"Bir olay tetiklenene kadar kod akışı nasıl engellenir?"

Yaklaşımınız yanlış. Olay güdümlü bir olayı engellemek ve beklemek anlamına gelmez. Asla beklemezsin, en azından bundan kaçınmak için her zaman çok uğraşırsın. Beklemek kaynakları israf etmek, iş parçacıklarını bloke etmek ve belki de bir kilitlenme veya zombi iş parçacığı riski getirmektir (serbest bırakma sinyalinin asla yükseltilmemesi durumunda). Bir olayı beklemek için bir
iş parçacığını engellemenin , bir olay fikrine aykırı olduğu için bir anti-kalıp olduğu açık olmalıdır .

Genellikle iki (modern) seçeneğiniz vardır: eşzamansız bir API veya olaya dayalı bir API uygulama. API'nizi eşzamansız olarak uygulamak istemediğiniz için, etkinliğe dayalı API'ya bırakılırsınız.

Olay güdümlü bir API'nin anahtarı, arayanı bir sonuç veya anket için eşzamanlı olarak beklemeye zorlamak yerine, sonuç hazır olduğunda veya işlem tamamlandığında arayanın devam etmesine ve ona bir bildirim göndermesine izin vermenizdir. Bu arada, arayan diğer işlemleri yürütmeye devam edebilir.

Soruna bir iş parçacığı perspektifinden bakıldığında, olaya dayalı API, çağrı yapan iş parçacığının, örneğin, düğmenin olay işleyicisini yürüten UI iş parçacığının, örneğin UI öğelerini oluşturma gibi diğer UI ile ilgili işlemleri işlemeye devam etmekte serbest olmasına izin verir. veya fare hareketi ve tuşa basma gibi kullanıcı girdilerinin işlenmesi. Olay güdümlü API, asenkron API ile aynı etkiye veya hedefe sahiptir, ancak çok daha az uygundur.

Gerçekten ne yapmaya çalıştığınız, gerçekte ne Utility.PickPoint()yaptığı ve görevin sonucunun ne olduğu veya kullanıcının neden `` Izgara'yı tıklaması gerektiğine dair yeterli ayrıntı vermediğiniz için size daha iyi bir çözüm sunamıyorum. . Gereksiniminizi nasıl uygulayacağınıza dair genel bir model sunabilirim.

Akışınız veya hedefiniz, bir dizi işlem yapmak için en az iki adıma ayrılmıştır:

  1. Kullanıcı düğmeyi tıkladığında 1. işlemi gerçekleştirin
  2. Kullanıcı aşağıdaki öğeyi tıkladığında 2. işlemi (devam / işlemi 1 tamamla) yürütün Grid

en az iki kısıtlamayla:

  1. İsteğe bağlı: API istemcisinin tekrarlamasına izin verilmeden önce sekansın tamamlanması gerekir. İşlem 2 tamamlanınca bir dizi tamamlanır.
  2. İşlem 1 her zaman işlem 2'den önce yürütülür. İşlem 1 diziyi başlatır.
  3. API istemcisinin işlem 2'yi yürütmesine izin verilmeden önce İşlem 1'in tamamlanması gerekir

Bu, API istemcisinin engellemeyen etkileşime izin vermesi için iki bildirim (olay) gerektirir:

  1. İşlem 1 tamamlandı (veya etkileşim gerekli)
  2. İşlem 2 (veya hedef) tamamlandı

İki genel yöntemi ve iki genel etkinliği göstererek API'nizin bu davranışı ve kısıtlamaları uygulamasına izin vermelisiniz.

Bu uygulama, API'ye yalnızca bir (eşzamanlı olmayan) çağrıya izin verdiğinden, bir IsBusyözelliği çalışan bir sırayı belirtecek şekilde göstermeniz de önerilir . Tamamlanan olayın sonraki çağrıları yürütmesini beklemeniz önerilir, ancak bu, yeni bir sıralamaya başlamadan önce mevcut durumun sorgulanmasını sağlar.

Uygulama / refactor Yardımcı Programı API'sı

Utility.cs

class Utility
{
  public event EventHandler InitializePickPointCompleted;
  public event EventHandler<PickPointCompletedEventArgs> PickPointCompleted;
  public bool IsBusy { get; set; }
  private bool IsPickPointInitialized { 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.IsBusy)
    {
      // 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.IsBusy = 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 (allow next client invocation)
    this.IsBusy = 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 : AsyncCompletedEventArgs 
{
  public Point Result { get; }

  public PickPointCompletedEventArgs(Point result)
  {
    this.Result = result;
  }
}

API'yı kullanma

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>

Uyarılar

Bir arka plan iş parçacığında oluşturulan olaylar, işleyicilerini aynı iş parçacığında yürütür. DispatcherObjectBir arka plan iş parçacığında yürütülen bir işleyiciden benzer bir UI öğesine erişmek için, kritik işlemin Dispatcherya iş parçacığına ya Dispatcher.Invokeda Dispatcher.InvokeAsyncçapraz iş parçacığı istisnalarını önlemek için kullanılması gerekir . Dağıtıcı afinitesi veya iplik afinitesi olarak adlandırılan bu fenomen hakkında daha fazla bilgi edinmek
için yorumları okuyun DispatcherObject.
API'nin rahat bir şekilde kullanılması için, yakalayan ve kullanarak SynchronizationContextya da AsyncOperation(veya AsyncOperationManager) kullanarak tüm olayları arayanın orijinal içeriğiyle birleştirmenizi öneririm .

Yukarıdaki örnek, örneğin bir Cancel()yöntemin gösterilmesi PickPointCancel()ve ilerleme raporlaması (tercihen kullanılarak Progress<T>) , iptal (tavsiye edilir) sağlanarak kolayca geliştirilebilir .


Bazı düşünceler - yorumlarınıza cevap verin

Konsol uygulamalarına örnek olarak "daha iyi" bir engelleme çözümü bulmam için bana yaklaştığınız için, sizi ikna ettiğimizi, algılayışınızın veya bakış açınızın tamamen yanlış olduğunu hissettim.

Msgstr "İçinde bu iki kod satırı bulunan bir Konsol uygulaması düşünün.

var str = Console.ReadLine(); 
Console.WriteLine(str);

Uygulamayı hata ayıklama modunda yürüttüğünüzde ne olur? İlk kod satırında duracak ve Konsol UI'sında bir değer girmeye zorlayacak ve daha sonra bir şey girip Enter tuşuna bastıktan sonra, bir sonraki satırı yürütecek ve girdiğiniz şeyi yazdıracaktır. Aynı davranışı düşünüyordum ama WPF uygulamasında. "

Bir konsol uygulaması tamamen farklı bir şeydir. Diş açma konsepti biraz farklıdır. Konsol uygulamalarının GUI'si yoktur. Sadece giriş / çıkış / hata akışları. Bir konsol uygulamasının mimarisini zengin bir GUI uygulamasıyla karşılaştıramazsınız. Bu işe yaramaz. Bunu gerçekten anlamalı ve kabul etmelisiniz.

Ayrıca görünüş tarafından aldatılma . İçinde neler olduğunu biliyor musun Console.ReadLine? Nasıl uygulanır ? Ana ipliği engelliyor mu ve paralel olarak girişi okuyor mu? Yoksa sadece yoklama mı?
İşte orijinal uygulama 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;
}

Gördüğünüz gibi basit bir senkron işlem. "Sonsuz" bir döngüde kullanıcı girişi için anket yapar. Sihirli blok yok ve devam et.

WPF, bir oluşturma iş parçacığı ve bir UI iş parçacığı etrafında oluşturulur. Bu iş parçacıkları , kullanıcı girişini işlemek gibi işletim sistemiyle iletişim kurmak için her zaman dönmeye devam eder - uygulamayı duyarlı tutar . Çerçevenin fare olaylarına yanıt vermek gibi temel arka plan çalışmasını durduracağı için bu iş parçacığını asla duraklatmak / engellemek istemezsiniz; farenin donmasını istemezsiniz:

bekleme = iş parçacığı engelleme = yanıt vermeme = kötü UX = rahatsız kullanıcılar / müşteriler = ofiste sorun.

Bazen, uygulama akışının girdinin veya bir yordamın tamamlanmasını beklemesi gerekir. Ama ana iş parçacığını engellemek istemiyoruz.
Bu yüzden insanlar ana iş parçacığını engellemeden ve geliştiriciyi karmaşık ve hatalı çok iş parçacıklı kod yazmaya zorlamadan karmaşık asenkron programlama modellerini icat ettiler.

Her modern uygulama çerçevesi, basit ve verimli kodun geliştirilmesine izin vermek için eşzamansız işlemler veya eşzamansız bir programlama modeli sunar.

Eşzamansız programlama modeline direnmeye çalıştığınız gerçeği bana biraz anlayış eksikliği gösteriyor. Her modern geliştirici, senkronize olmayan bir API yerine senkronize olmayan bir API'yi tercih eder. Hiçbir ciddi geliştirici awaitanahtar kelimeyi kullanmaya veya yöntemini bildirmeye özen göstermez async. Kimse. Eşzamansız API'lardan şikayet eden ve bunları kullanmakta uygun olmayan kimseyle karşılaştığım ilk kişisin.

Kullanıcı arayüzüyle ilgili sorunları çözmeyi veya kullanıcı arayüzüyle ilgili görevleri kolaylaştırmayı hedefleyen çerçevenizi kontrol edersem, bunun eşzamansız olmasını beklerdim .
Eşzamansız olmayan UI ile ilgili API, programlama tarzımı karmaşıklaştıracağından israftır, bu nedenle kodum daha hata eğilimli ve bakımı zor hale gelir.

Farklı bir bakış açısı: Beklemenin UI iş parçacığını engellediğini kabul ettiğinizde, UI bekleme bitene kadar donacağı için çok kötü ve istenmeyen bir kullanıcı deneyimi yaratıyor, şimdi bunu fark edersiniz, neden bir API veya eklenti modeli sunuyorsunuz? bir geliştiriciyi tam olarak bunu yapmaya teşvik eder - beklemeyi uygula?
Üçüncü taraf eklentisinin ne yapacağını ve bir rutinin tamamlanana kadar ne kadar süreceğini bilmiyorsunuz. Bu sadece kötü bir API tasarımı. API'niz UI iş parçacığında çalıştığında, API'nızın arayanı buna engellemeyen çağrılar yapabilmelidir.

Tek ucuz veya zarif çözümü reddederseniz, örneğimde gösterildiği gibi olaya dayalı bir yaklaşım kullanın.
İstediğinizi yapar: bir rutin başlatın - kullanıcı girişini bekleyin - yürütmeye devam edin - hedefi gerçekleştirin.

Beklemenin / engellemenin neden kötü bir uygulama tasarımı olduğunu açıklamak için birkaç kez denedim. Yine, bir konsol kullanıcı arayüzünü zengin bir grafik kullanıcı arayüzüyle karşılaştıramazsınız; burada yalnızca giriş işleme, yalnızca giriş akışını dinlemekten çok daha karmaşıktır. Deneyim düzeyinizi ve nereden başladığınızı gerçekten bilmiyorum, ancak eşzamansız programlama modelini benimsemeye başlamalısınız. Bundan kaçınmaya çalışmanın nedenini bilmiyorum. Ama hiç de akıllıca değil.

Bugün eşzamansız programlama modelleri her yerde, her platformda, derleyicide, her ortamda, tarayıcıda, sunucuda, masaüstünde, veritabanında - her yerde uygulanmaktadır. Olay güdümlü model aynı hedefe ulaşmaya izin verir, ancak arka plan iş parçacıklarına dayanarak kullanmak (olaylara abone olmak / olaylardan abonelikten çıkmak, belgeleri okumak (dokümanlar olduğunda)) okumak daha az uygundur. Olay güdümlü eski modadır ve yalnızca eşzamansız kütüphaneler mevcut olmadığında veya uygulanabilir olmadığında kullanılmalıdır.

Bir yan not olarak: .NET Framwork (.NET Standard), TaskCompletionSourcevar olan çift sürücülü bir API'yi eşzamansız bir API'ye dönüştürmek için basit bir yol sunmayı (diğer amaçların yanı sıra ) sunar .

"Autodesk Revit'te tam davranışı gördüm."

Davranış (deneyimlediğiniz veya gözlemlediğiniz şey) bu deneyimin nasıl uygulandığından çok farklıdır. İki farklı şey. Autodesk, büyük olasılıkla eşzamansız kitaplıklar veya dil özellikleri veya başka bir iş parçacığı mekanizması kullanıyor olabilir. Ayrıca bağlamla da ilgilidir. Aklınızdaki yöntem bir arka plan iş parçacığı üzerinde yürütülürken, geliştirici bu iş parçacığını engellemeyi seçebilirsiniz. Bunu yapmak için çok iyi bir nedeni var ya da sadece kötü bir tasarım seçimi yaptı. Tamamen yanlış yoldasınız;) Engelleme iyi değil.
(Autodesk kaynak kodu açık kaynak mıdır? Yoksa nasıl uygulandığını nereden biliyorsunuz?)

Seni gücendirmek istemiyorum, lütfen inan bana. Ancak lütfen API'nızı zaman uyumsuz olarak uygulamayı yeniden düşünün. Geliştiriciler async / await kullanmaktan hoşlanmıyorlar. Açıkça yanlış zihniyete sahipsin. Ve bu konsol uygulama argümanını unutun - bu saçmalık;)

UI ilgili API GEREKİR Mümkün bekliyoruz zaman uyumsuz / kullanın. Aksi takdirde, API'nizin istemcisine engellemeyen kod yazmak için tüm işleri terk edersiniz. Beni API'nize yapılan her çağrıyı bir arka plan iş parçacığına sarmaya zorlarsınız. Veya daha az konforlu olay işleme özelliğini kullanmak için. İnanın - her geliştirici async, etkinlik yönetimi yerine üyelerini süslemektedir . Olayları her kullandığınızda olası bir bellek sızıntısı riskiyle karşı karşıya kalabilirsiniz - bazı koşullara bağlıdır, ancak dikkatsizlik programlanırken risk gerçek değildir ve nadir değildir.

Umarım engellemenin neden kötü olduğunu anlarsın. Umarım modern bir eşzamansız API yazmak için eşzamansız / beklemeyi kullanmaya karar vermişsinizdir. Yine de, async / await kullanmanızı tavsiye etsem de, olayları kullanarak engellememeyi beklemenin çok yaygın bir yolunu gösterdim.

"API, programcının kullanıcı arayüzüne vb. Erişmesine izin verecektir. Şimdi programcının bir düğmeye tıklandığında son kullanıcıdan kullanıcı arayüzünde bir nokta seçmesinin istendiği bir eklenti geliştirmek istediğini varsayalım"

Eklentinin UI öğelerine doğrudan erişmesine izin vermek istemiyorsanız, olayları devretmek veya dahili bileşenleri soyutlanan nesneler aracılığıyla ortaya çıkarmak için bir arabirim sağlamalısınız.
API dahili olarak Eklenti adına UI olaylarına abone olur ve ardından API istemcisine karşılık gelen bir "sarmalayıcı" olayı göstererek etkinliği temsil eder. API'niz, belirli uygulama bileşenlerine erişmek için Eklentinin bağlanabileceği bazı kancalar sunmalıdır. Bir eklenti API'si, harici cihazların dahili cihazlara erişimini sağlamak için bir adaptör veya cephe görevi görür.
Bir dereceye kadar izolasyona izin vermek için.

Visual Studio'nun eklentileri nasıl yönettiğine veya bunları uygulamamıza nasıl izin verdiğine bir göz atın. Visual Studio için bir eklenti yazmak ve bunun nasıl yapılacağı konusunda biraz araştırma yapmak istediğinizi varsayalım. Visual Studio'nun dahili bileşenlerini bir arabirim veya API aracılığıyla açtığını fark edeceksiniz. EG, kod düzenleyiciyi değiştirebilir veya gerçek erişim olmadan düzenleyicinin içeriği hakkında bilgi alabilirsiniz .


Merhaba, soruya başka bir açıdan yaklaştığınız için teşekkür ederim. Sorunun ayrıntılarda biraz belirsiz olup olmadığını özür dilerim. İçinde bu iki kod satırı bulunan bir Konsol uygulaması düşünün. var str = Console.ReadLine(); Console.WriteLine(str);Uygulamayı hata ayıklama modunda yürüttüğünüzde ne olur? Kodun ilk satırında duracak ve Konsol UI'sında bir değer girmeye zorlayacak ve daha sonra bir şey girip Enter tuşuna bastıktan sonra bir sonraki satırı yürütecek ve girdiğiniz şeyi yazdıracaktır. Tam olarak aynı davranışı düşünüyordum ama WPF uygulamasında.
Vahid

Geliştirdiğim CAD uygulamasında, kullanıcıların eklentiler / eklentilerle genişletebilmeleri gerekiyor. API, programcının kullanıcı arayüzüne vb. Erişmesine izin verecektir. Şimdi programcının bir düğmeye tıklandığında son kullanıcıdan kullanıcı arayüzünde bir nokta seçmesinin ve ardından kodun verilen nokta ile harika şeyler. Belki başka bir noktanın seçilmesini ve çizgi çizmesini vb.
İsteyeceklerdir

Autodesk Revit'te tam davranışı gördüm.
Vahid

1
İhtiyacınız hakkında söyleyecek bir şeyim var. Lütfen güncellenmiş cevabımı okuyun. Cevabı orada yayınladım çünkü bir şekilde uzadı. Beni gerçekten tetiklediğini itiraf ediyorum. Lütfen, okurken sizi rahatsız etmek istemediğimi unutmayın.
BionicCode

Güncellenmiş cevabınız için teşekkür ederim. Tabii ki, rahatsız değilim. Aksine, bu konuya verdiğiniz zaman ve çaba için gerçekten minnettarım.
Vahid

5

Şahsen bunun herkes tarafından aşırı karmaşık olduğunu düşünüyorum, ama belki de bunun belirli bir şekilde yapılması gerekmesinin nedenini tam olarak anlayamıyorum, ancak burada basit bir bool kontrolü kullanılabilir gibi görünüyor.

Her şeyden önce, Backgroundve IsHitTestVisibleözelliklerini ayarlayarak ızgaralarınızı isabetli hale getirin , aksi takdirde fare tıklamalarını bile yakalamaz.

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

Ardından, "GridClick" olayının gerçekleşip gerçekleşmeyeceğini saklayabilecek bir bool değeri oluşturun. Izgara tıklandığında, bu değeri ve tıklamayı bekliyorsa ızgara tıklama olayından yürütme işlemini kontrol edin.

Misal:

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
}

Merhaba Tronald, sanırım soruyu yanlış anladınız. Ne ihtiyacım kod Utility.PickPoint (Görünüm) durdurmak ve yalnızca kullanıcı Grid tıkladıktan sonra devam etmektir.
Vahid

Ah evet, tamamen yanlış anladım. Özür dilerim, aslında durdurmak için her şeye ihtiyacın olduğunu fark etmedim. Tüm kullanıcı arayüzü engelleneceği için çoklu iş parçacığı olmadan bunun mümkün olduğunu düşünmüyorum.
Tronald

Hala mümkün olup olmadığından emin değilim. Çok iş parçacıklı bir çözüm olmayan async / await ile kesinlikle mümkündür. Ama ihtiyacım olan şey, async / await çözümüne bir alternatif.
Vahid

1
Tabii, ama async / beklemede kullanamayacağınızı söylemiştiniz. Bir dağıtıcı ve ana iş parçacığından (UI üzerinde çalışan) ayrılmış bir iş parçacığından faydalanmanız gerektiği anlaşılıyor. Umarım kendimi ilgilendirirken başka bir yol bulursun
Tronald

2

Birkaç şey denedim ama onsuz yapamıyorum async/await. Çünkü bunu kullanmazsak, neden olur DeadLockveya kullanıcı arayüzü engellenir ve daha sonra Grid_Clickgirdi alma imkanımız olur.

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

    }
}

@ teşekkürler, bu async / await kullanan diğer sorum için aldığım cevapla aynı.
Vahid

Oh evet ! şimdi fark ettim ama çalıştığını bulduğum tek yol sanırım
Rao Hammas Hussain

2

Aşağıdakileri kullanarak eşzamansız olarak engelleyebilirsiniz 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();
}

Gönderici iş parçacığını eşzamanlı olarak engelleyemezsiniz, çünkü o zaman hiçbir zaman tıklamayı işleyemez Grid, yani hem engellenemez hem de olayları aynı anda işleyemez.


Alternatif bir cevap için teşekkürler. Mesela Console.Readline () 'da nasıl yapıldığını merak ediyorum. Hata ayıklayıcıda bu yönteme ulaştığınızda, bir şey girmedikçe sihirli bir şekilde orada durur mu? Konsol uygulamalarında temel olarak farklı mıdır? WinForms / WPF uygulamasında aynı davranışa sahip olamaz mıyız? Autodesk Revit'in API'sinde bunu gördüm, ekranda bir nokta seçmeye zorlayan bir PickPoint () yöntemi var ve herhangi bir async / bekleyen kullanmadım! En azından await anahtar sözcüğünü gizlemek ve bir şekilde bir senkronizasyon yönteminden çağırmak mümkün mü?
Vahid

@Vahid: Console.Readline bloklar , yani bir satır okunana kadar geri dönmez. Kişisel PickPointyöntem değildir. Hemen geri döner. Potansiyel olarak engelleyebilir, ancak daha sonra cevabımda yazdığım gibi UI girişini işleyemezsiniz. Başka bir deyişle, aynı davranışı elde etmek için yöntemin içindeki tıklamayı işlemeniz gerekir.
mm8

Console.Realine () engeller, ancak aynı zamanda KeyPress olaylarına izin verilir. Burada tam olarak aynı davranışı gösteremez miyiz? PickPoint () ile engelleme ve yalnızca MouseEvents'a izin veriyor musunuz? Konsolda neden mümkün olduğunu anlayamıyorum, ancak UI tabanlı bir uygulamada değil.
Vahid

Ardından PickPoint, fare olaylarını işleyen ayrı bir dağıtıcı kurmanız gerekir . Bununla nereye gittiğini göremiyorum?
mm8

1
@Vahind: Kodu zaman uyumsuz hale getirin ve kullanıcının yöntemi beklemesine izin verin. Bu, bir UI geliştiricisi olarak bekleyeceğim API. Bir UI uygulamasında engelleme yöntemini çağırmak mantıklı değildir.
mm8

2

Teknik olarak AutoResetEventve onsuz mümkündür async/await, ancak önemli bir dezavantaj vardır:

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...
}

Dezavantajı: Örnek kodunuzun yaptığı gibi bu yöntemi doğrudan bir düğme olay işleyicisinde çağırırsanız, kilitlenme oluşur ve uygulamanın yanıt vermeyi durdurduğunu görürsünüz. Kullanıcının tıklamasını beklemek için tek kullanıcı arayüzü iş parçacığını kullandığınız için, kullanıcının ızgaraya tıklaması da dahil olmak üzere hiçbir kullanıcının eylemine yanıt veremez.

Yöntem tüketicileri, kilitlenmeleri önlemek için başka bir iş parçacığında çağırmalıdır. Eğer garanti edilebilirse, sorun değil. Aksi takdirde, yöntemi şu şekilde çağırmanız gerekir:

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

Kendi iş parçacıklarını yönetmek için kullandıkları dışında API'nızın tüketicileri için daha fazla sorun yaratabilir. Bu yüzden async/awaiticat edildi.


Teşekkür ederim Ken, eklentinin başka bir iş parçacığından başlayıp olaylarının ana kullanıcı arabirimi iş parçacığını engellememesi mümkün mü?
Vahid

@Vahid Evet ve hayır. Evet, engelleme yöntemini başka bir iş parçacığında çağırabilir ve başka bir yöntemde sarabilirsiniz. Bununla birlikte, sarma yönteminin, UI engellemesini önlemek için UI iş parçacığı dışında başka bir iş parçacığında çağrılması gerekiyordu. Sarıcı engeller Çünkü çağıran iş parçacığı Eğer öyleyse senkron . Sargı içten başka bir iş parçacığını engellemesine rağmen, yine de sonucu beklemek ve iş parçacığı çağırmayı engellemek gerekir. Arayan UI iş parçacığında sarma yöntemini çağırırsa, UI engellenir.
Ken Hung

0

Sorun tasarımın kendisinde olduğunu düşünüyorum. API'niz belirli bir öğede çalışıyorsa, başka bir öğede değil, bu öğenin olay işleyicisinde kullanılmalıdır.

Örneğin, burada Grid'deki click olayının konumunu almak istiyoruz, API'nin düğme öğesinde değil, Grid öğesindeki olayla ilişkili olay işleyicide kullanılması gerekir.

Şimdi, gereksinim Izgaraya tıklamayı yalnızca Düğmeye tıkladıktan sonra işlemekse, Düğme sorumluluğu Izgaraya olay işleyicisini eklemek olacaktır ve Izgaradaki tıklama olayı mesaj kutusunu gösterecek ve kaldıracaktır. bu olay işleyicisi, bu tıklamadan sonra artık tetiklenmemesi için düğme tarafından eklendi ... (UI İş Parçacığını engellemeye gerek yok)

Sadece düğme tıklamasında UI iş parçacığını engellerseniz, UI iş parçacığının daha sonra Izgara'daki click olayını tetikleyebileceğini düşünmüyorum.


0

Her şeyden önce, UI iş parçacığı, ilk sorunuzdan aldığınız cevap gibi blok olamaz.
Bunu kabul ediyorsanız, müşterinizin daha az değişiklik yapmasını sağlamak için zaman uyumsuz / beklemekten kaçınmak yapılabilir ve hatta herhangi bir çoklu iş parçacığına gerek yoktur.

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

Ancak UI iş parçacığını veya "kod akışını" engellemek istiyorsanız, bunun cevabı imkansız olacaktır. Çünkü UI iş parçacığı engellenmişse, başka bir girdi alınamaz.
Konsol uygulamasından bahsettiğiniz için, basit bir açıklama yapıyorum.
Bir konsol uygulaması çalıştırdığınızda veya AllocConsoleherhangi bir konsola (pencere) bağlı olmayan bir işlemden çağrı yaptığınızda, bir konsol (pencere) sağlayabilecek conhost.exe yürütülür ve konsol uygulaması veya arayan işlemi konsola eklenir ( pencere).
Bu nedenle, arayanın iş parçacığını engelleyebilecek herhangi bir kod Console.ReadKey, konsol penceresinin UI iş parçacığını engellemez, konsol uygulaması girişinizi beklerken, yine de fare tıklaması gibi diğer girişlere yanıt verebilmesinin nedeni budur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.