"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:
- Kullanıcı düğmeyi tıkladığında 1. işlemi gerçekleştirin
- 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:
- İsteğe bağlı: API istemcisinin tekrarlamasına izin verilmeden önce sekansın tamamlanması gerekir. İşlem 2 tamamlanınca bir dizi tamamlanır.
- İşlem 1 her zaman işlem 2'den önce yürütülür. İşlem 1 diziyi başlatır.
- 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:
- İşlem 1 tamamlandı (veya etkileşim gerekli)
- İş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. DispatcherObject
Bir arka plan iş parçacığında yürütülen bir işleyiciden benzer bir UI öğesine erişmek için, kritik işlemin Dispatcher
ya iş parçacığına ya Dispatcher.Invoke
da 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 SynchronizationContext
ya 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 await
anahtar 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), TaskCompletionSource
var 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 .
Aync/Await
A İş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 ??