Eşzamansız EventHandler'ları tartıştığımızda çoğumuzun aklına gelen ilk şey, bunun izin verdiğimiz tek istisna olduğudur.
Bunun hakkında daha önce yazdığımda, asenkron boşluğun var olmasına izin veren (saçımın geri kalanını yolmak istemeden) bir çözümü araştırdığım için heyecanlanmıştım.
Benim için bu, sorunu tamamen ortadan kaldıracak çözümler sunmaktan çok, eşzamansız EventHandler'ların üstesinden gelmek için kullanabileceğimiz bazı akıllı hilelerle ilgiliydi.
Bununla birlikte, makale üzerinde çok fazla ilgi vardı, buna çok müteşekkirim ve bazı kişiler async EventHandlers'ı farklı bir şekilde çözmeyi tercih edecekleri yönünde görüşlerini ifade ettiler.
Bunun harika bir nokta olduğunu düşündüm, bu yüzden eşzamansız boşluğu düzeltmeyen alternatif bir yaklaşım bulmak istedim, ancak bu, bazı zorlukları çözerken tamamen geçersiz kılmanıza (orada ne yaptığımı gördünüz mü?) olanak tanıyor eşzamansız EventHandlers ile.
Bu yazımda kendi kodunuzda deneyebileceğiniz başka bir çözüm sunacağım. Nasıl kullanılabileceğine ilişkin olarak artılarını ve eksilerini benim bakış açımdan ele alacağız, böylece kullanım durumunuz için anlamlı olup olmadığına karar verebilirsiniz.
Ayrıca .NET keman sağında bazı etkileşimli kodlar da bulabilirsiniz.
Eşzamansız EventHandlers ile karşılaştığımız sorun, C#'ta abone olabileceğimiz olayların imzasının varsayılan olarak şöyle görünmesidir:
void TheObject_TheEvent(object sender, EventArgs e);
Ve fark edeceksiniz ki, bu imzanın ön yüzünü geçersiz kılarak, etkinliğe abone olmak için kendi işleyicilerimizde void kullanmak zorunda kalıyoruz.
Bu, eğer işleyicinizin eşzamansız/beklemede kod çalıştırmasını istiyorsanız, geçersiz yönteminizin içinde beklemeniz gerektiği anlamına gelir… Bu, veba gibi kaçınmamız söylenen büyük, korkutucu eşzamansız boşluk modelini ortaya çıkarır.
Ve neden? Çünkü async void, istisnaların düzgün bir şekilde ortaya çıkma yeteneğini bozar ve sonuç olarak bir sürü baş ağrısına neden olabilir.
Bana göre basit olan daha iyidir… yani eşzamansız geçersizlik hakkındaki önceki makalemi okursanız ve amacınız gerçekten sadece EventHandlers ile uğraşmaksa, bu size yardımcı olacaktır.
Daha önce belirtilen koşullara bağlı olarak, istisna işleme, zaman uyumsuz boşluğun sınırlarını aşarak bozulur. Bu sınırı aşması gereken bir istisna varsa, o zaman eğlenceli vakit geçireceksiniz.
Eğlence derken, eğer bir şeyin neden çalışmadığını tespit etmekten hoşlanıyorsanız ve neyin bozulduğuna dair net bir fikriniz yoksa, o zaman gerçekten harika vakit geçireceksiniz.
Peki bunu düzeltmenin en kolay yolu nedir?
Erişebildiğimiz basit bir aracı kullanarak istisnaların bu sınırı aşmasını ilk etapta önleyelim: dene/yakala.
objectThatRaisesEvent.TheEvent += async (s, e) => { // if the try catch surrounds EVERYTHING in the handler, no exception can bubble up try { await SomeTaskYouWantToAwait(); } catch (Exception ex) { // TODO: put your exception handling stuff here } // no exception can escape here if the try/catch surrounds the entire handler body }
Yukarıdaki kodda belirtildiği gibi, olay işleyicinizin ENTIRE gövdesinin etrafına bir try/catch bloğu yerleştirirseniz, bu zaman uyumsuz boşluk sınırı boyunca herhangi bir istisnanın ortaya çıkmasını önleyebilirsiniz. Görünüşte oldukça basittir ve bunu uygulamak için süslü bir şey gerektirmez.
Artıları:
Eksileri:
Bununla birlikte, bu çözüm gerçekten basit, ancak biraz daha iyisini yapabileceğimizi düşünüyorum.
Başlangıçta önerilen çözümün üzerine yapabileceğimizi düşündüğüm bir gelişme, istisnaların oluşmasına karşı güvenli olması gereken eşzamansız bir EventHandler'a sahip olduğumuzu biraz daha açık hale getirebilmemizdir.
Bu yaklaşım aynı zamanda zaman içinde kod kaymasının sorunlu kodun olay işleyicinin dışında çalışmasına neden olmasını da önleyecektir. Ancak bunu manuel olarak eklemeyi hatırlamanız gerektiği gerçeğini ele almayacaktır!
Kodu kontrol edelim:
static class EventHandlers { public static EventHandler<TArgs> TryAsync<TArgs>( Func<object, TArgs, Task> callback, Action<Exception> errorHandler) where TArgs : EventArgs => TryAsync<TArgs>( callback, ex => { errorHandler.Invoke(ex); return Task.CompletedTask; }); public static EventHandler<TArgs> TryAsync<TArgs>( Func<object, TArgs, Task> callback, Func<Exception, Task> errorHandler) where TArgs : EventArgs { return new EventHandler<TArgs>(async (object s, TArgs e) => { try { await callback.Invoke(s, e); } catch (Exception ex) { await errorHandler.Invoke(ex); } }); } }
Yukarıdaki kod, istisnaların eşzamansız boşluk sınırını geçmesini önlemek için kelimenin tam anlamıyla aynı yaklaşımı kullanır. Biz sadece olay işleyicisinin gövdesini yakalamaya çalışıyoruz, ancak şimdi bunu yeniden kullanıma özel bir yöntemle bir araya getirdik.
Bunu uygulamanın nasıl görüneceği aşağıda açıklanmıştır:
someEventRaisingObject.TheEvent += EventHandlers.TryAsync<EventArgs>( async (s, e) => { Console.WriteLine("Starting the event handler..."); await SomeTaskToAwait(); Console.WriteLine("Event handler completed."); }, ex => Console.WriteLine($"[TryAsync Error Callback] Our exception handler caught: {ex}"));
Artık birlikte çalışabileceğimiz eşzamansız Görev imzasına sahip bir temsilcimiz olduğunu ve içine koyduğumuz her şeyin, daha önce gördüğümüz yardımcı yöntem dahilinde bir deneme/yakalama işlemine sahip olacağından emin olduğumuzu görebiliriz.
Aşağıda, istisnayı düzgün bir şekilde yakalayan hata işleyici geri aramasını gösteren bir ekran görüntüsü verilmiştir:
Artıları:
Eksileri:
Başlangıçta keşfetmeye çıktığımda
Bu makalede, async EventHandler'larınızın düzgün davranmasını sağlamanın en basit yolunun ne olduğunu tartışabileceğimi araştırdık ve geliştirilmiş çözümün (bence) yalnızca onu kullanmayı hatırlamanız gereken dezavantajı var.
Bir yorumcu kişinin keşfedebileceğini önermişti
Bazı derleme zamanı AoP çerçeveleri mevcut, ancak bunu okuyucu olarak size bir alıştırma olarak bırakacağım (çünkü bu aynı zamanda benim için de takip etmem gereken bir alıştırmadır).