Asenkron Programlamada Hata Yönetimi

     Asenkron programlamanın detaylarına indiğimiz makale serisine başlarken sizlere async ve await sayesinde neredeyse aynı  senkron programlar gibi asenkron programlama yapabiliyor olduğumuzdan bahsetmiştim. Bu benzerliğe belki tek/en önemli istisna ise hata yönetimidir. Bu makalemde, asenkron programlama ile uğraşacaksanız kesinlikle bilmeniz ve dikkat etmeniz gereken, hata yönetiminin detaylarını sizinle paylaşıyor olacağım.

    Asenkron programlamadaki hata yönetiminin senkron programlamadan hangi noktalarda farklı olduğu, nelere dikkat edilmesi gerektiğini bilmek için öncelikle hata yönetiminin senkron programlamada nasıl olduğuna bir göz atalım.

    Yazılımlarda oluşan hatalar, her zaman olmasa da çoğunlukla, uygulamanın tutarsız bir durumda kalmasına neden olmaktadır. Hafızada açılan kaynaklar ya da oluşturulan diğer geçici kaynaklar iade edilmemiş, dosyalar üzerinde yapılan değişiklikler yarım kalmış, hatta oluşturulan lock’lar zamansız olarak bırakılmış olabilir. Dolayısıyla da uygulamanızda yakalanmamış bir hata oluşması sonrasında uygulamanızın tam olarak hangi durumda kaldığı ya da ne kadar tutarlı bir durumda olduğu kesin olarak bilinemeyecektir. Her ne kadar tüm hatalar uygulamanızı biraz önce bahsettiğim bu tutarsız duruma sokmayacak olsa da şunu kabul etmeliyiz ki CLR gibi bir altyapı geliştirirken, geliştiricilerin bunu biliyor olması da imkansızdır. Dolayısıyla da bu gibi durumlarda seçilecek en güvenli yol yakalanmamış tüm  hataların süreci tutarsız bir duruma soktuğu varsayımıdır. Tutarsız bir duruma giren sürecin ise sistemin genel tutarlılığının korunması ve güvenlik gibi gerekçelerle derhal sonlandırılması gereklidir.

    CLR’ın ilk sürümünü incelediğimizde; bir uygulamanın ana iş parçacığında oluşan ve hiç bir şekilde programsal olarak yakalanarak yönetilmemiş olan hatalarda sistem derhal uygulamayı sonlandırmaktaydı. Buna karşılık, ana iş parçacığı dışında oluşturulan diğer tüm iş parçacıklarında oluşan hatalarda ise sadece ilgili iş parçacığı sonlandırılarak uygulamanın çalışmasına devam etmesine izin verilmekteydi. Bu durumun, ilgili iş parçacığı derhal sonlandırılsa bile, uygulamanın genelinde bir tutarsızlık oluşturduğu ve kimi zaman da önemli güvenlik zafiyetleri ortaya çıkardığı fark edilince takip eden sürümde değiştirildi. CLR 2.0 ile birlikte; varsayılan olarak uygulamanın ana ya da yan iş parçacıklarının herhangi birisinde yakalanmamış bir hata olması durumunda uygulama derhal sonlandırılmakta, dolayısıyla da sistemin tutarlılığı ve güvenliği de arttırılmış oldu.

   CLR üzerinde hataların ne şekilde yönetildiğini gördükten sonra isterseniz bir de asenkron programlamada bu durumu inceleyelim; Asenkron bir kod parçacığında hata oluşması durumunda nasıl hareket edilmeli? Örneğin WhenAll ya da WhenAny ile başlatılan işler bekleniyorsa ve bu işlerden bir ya da bir kaçında hata oluşursa nasıl bildirilir?

   WhenAll ile tüm işlerin bitmesini beklerken, bu işlerden oluşan hatalar beklenen tüm işlerin bitmesi sonrası bize toplu olarak iletilecektir. Burada dikkat edilmesi gereken nokta; aslında alınan hata mesajının sadece ilk oluşan hataya ait olmasıdır. Dolayısıyla oluşan tüm hataların tespit edilebilmesi için daha farklı bir sürecin takip ediliyor olması gereklidir. İlk hatanın gönderilmesi nedeniyle catch bloklarında sadece ilk hata ile ilgili olan blokta yer alan kod işletilecektir. Daha doğru bir hata yakalama için genel bir catch ekleyerek içerisinde tüm işlemlerin durumlarının kontrol edilmesi gerekecektir.

try {
    Task<string> task1 = new WebClient().DownloadStringTaskAsync("http://www.enterprisecoding.com/");
    Task<string> task2 = new WebClient().DownloadStringTaskAsync("http://olmayan-bir-url");
    Task<string> task3 = new WebClient().DownloadStringTaskAsync("http://olmayan-baska-bir-url");
 
    await TaskEx.WhenAll(task1, task2, task3);
}
catch (Exception ex) {
    //yakalanan hata ile ilgili iş mantığı
}

    Yukarıdaki örnek kodu inceleyecek olursak, uygulamanın çalışması sırasında verilen 3 işten 2’si geçersiz url nedeniyle hata vererek başarısız olacaktır; fakat catch ifadesinin içerinde bu hatalardan sadece 3.satırdaki işin çalışması sonucu oluşan hata gelecektir. Bu durumda da 4. satırdaki işin de başarısız olduğuna dair bir bilgi alınamayacak ve programsal olarak yakalanamayan bir hata oluşacaktır. Bu durumu tam olarak düzeltmemekle birlikte, 4. satırdaki işleme ilişkin hata bilgisine aşağıdaki şekilde ulaşılabilir;

Task<string> task1 = null;
Task<string> task2 = null;
Task<string> task3 = null;
try {
    task1 = new WebClient().DownloadStringTaskAsync("http://www.enterprisecoding.com/");
    task2 = new WebClient().DownloadStringTaskAsync("http://olmayan-bir-url");
    task3 = new WebClient().DownloadStringTaskAsync("http://olmayan-baska-bir-url");

    await TaskEx.WhenAll(task1, task2, task3);
}
catch (Exception) {
    if (task1 != null && task1.Exception != null) {
        //ilk işe ilişkin yakalanan 
        //hata ile ilgili iş mantığı
    }

    if (task2 != null && task2.Exception != null) {
        //ikinci işe ilişkin yakalanan 
        //hata ile ilgili iş mantığı
    }

    if (task3 != null && task3.Exception != null) {
        //üçüncü işe ilişkin yakalanan 
        //hata ile ilgili iş mantığı
    } 
}

   Task’lar üzerinden ulaşılabilen Exception özelliği yakından incelenecek olursa AggregateException türünden oldukları görülecektir. AggregateException yardımıyla ilgili iş içerisinde çalışan tüm alt işlerde oluşan ve yakalanmamış tüm hatalara ulaşılabilmektedir.

   WhenAny için de durum yukarıda paylaştıklarımdan farklı olmayacaktır. İlk işin bitmesi sonrasında işleyişin bir alt satıra geçmesine izin verilecek ve buradaki iş mantıkları çalıştırılacaktır; fakat bu sırada da diğer işler çalışmalarına devam edecektir. Peki devam eden bu işlerden bir ya da bir kaçında hata oluşması durumunda nasıl hareket edilecek?

   Her iki senaryoda da senkron bir programlamadan farklı olan böylesi durumlarda hata yakalandığında çoktan kodun devamındaki iş mantığı işletilmiş ve belki de uygulamada tutarsız bir duruma düşerek işleyişine devam etmiş olacak ya da yukarıdaki ilk örnekte olduğu gibi bazı hatalar programsal olarak fark edilemeden uygulama işleyişine devam edecektir.

   Eğer senkron ve asenkron hata yakalama mantıkları birbirinin aynı olsaydı, yazımın başında sizlere anlattığım CLR hata yakalama mantığına göre her iki durumda da oluşan hatalar yakalanmamış/tespit edilememiş olacağından uygulamanın işleyişi derhal durdurulmalıydı. Yukarıda yer alan örnekte de görüleceği gibi asenkron programlamada bu senaryolar oldukça sık oluşabilir ve yakalanamayan bu hatalar her zaman için uygulamanın tutarlılığı açısından kritik önemde olmayabilir. Senkron programlamadaki hata yakala mantığının işletilmesi gerektiğini düşünelim; peki yukarıdaki WhenAll ve WhenAny örneklerini düşünürsek; hangi noktada hata yakalanabilecek? Hata yakalandığı sırada büyük olasılıkla da çoktan uygulama tutarsız bir duruma düşmüş ve artık çalıştırılmaması gereken bir devam kodu çalıştırılmış olabilir. Yani istesek de istemesek de CLR hatayı fark edip uygulamayı sonlandırdığında çoktan tutarsız durumda uygulamamız kodlarını işletmiş olacaktır. Bu durumda da artık uygulamanın sonlandırılması pek bir anlam ifade etmeyecektir. Ek olarak asenkron programlamadaki her hata senkrondaki kadar kritik önem arz etmeyebilir. Async tasarlanırken işte tam bu mantık ile hareket edilmekte ve asenkron işlemler sırasında yakalanamayan hatalar nedeniyle uygulama sonlandırılmamaktadır.

     Paylaşmış olduğum bu bilgiler doğrultusunda, özellikle asenkron programlama sırasında, oluşabilecek hatalara karşı çok daha dikkatli davranarak uygulamanızın tutarsız bir durumda çalışmasının ve hatta güvenlik açıklarının bulunmasının önüne geçmenizin önemi çok daha fazla olacaktır. C# bir sonraki sürümüyle birlikte pek çok yazılımın gözden geçirilerek asenkron olarak kodlanacağını düşünecek olursak, bu uygulamaların oldukça dikkatli kodlanması ve test edilmesinin olası yeni hataların önüne geçebilmek adına ne kadar önemli olacağı görülecektir.

    Son olarak; mevcut haliyle, standart senkron hata yakalama mantığından farklı hareket eden ve yukarıda özetlemeye çalıştığım asenkron işlemlerinin henüz CTP seviyesinde olduğunu unutmayarak ilerleyen sürümlerde bu yaklaşımın değişebileceğini not düşmeliyim.

Fatih Boy

Ankara'da yaşayan Fatih, bir kamu kurumunda danışman olarak çalışmaktadır. ALM süreçleri, kurumsal veri yolu sistemleri, kurumsal altyapı ve yazılım geliştirme konularında destek vermektedir. Boş zamanlarında açık kaynak kodlu projeler geliştirmeyi ve bilgisini yazdığı makalelerle paylaşmayı seven Fatih, aynı zamanda Visual C# ve Visual Studio teknolojileri konusundan Microsoft tarafından altı yıl üst üste MVP (En Değerli Profesyonel) ödülüne layık görülmüştür. İş hayatı boyunca masaüstü uygulamaları, web teknolojileri, akıllı istemciler gibi konularda Asp.Net, Php, C#, Java programlama dilleri ve MySql, MsSql ve Oracle gibi veritabanı yönetim yazılımları ile çalışmıştır. İngilizce ve Türkçe olarak yayınlanan makalelerini gerek İngilizce bloğunda, gerekse de Türkçe bloğunda bulabileceğiniz gibi web sitesinden de açık kaynak kodlu geliştirdiği yazılımlarına ulaşabilirsiniz. vCard - Twitter - Facebook - Google+

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir