Kafayı yiyecektim arkadaşlar. Basit bir masaüstü uygulaması yazıyordum, WPF ile. Tabii ki ilk prensip: UI thread'i asla bloklama! O yüzden her uzun sürecek işi, her dosya okumasını, her API çağrısını hemen bir Task.Run'ın içine atıyordum. "Arayüz donmasın, kullanıcı mutlu olsun" diye.
Meğerse kendi tuzağımı kendim kuruyormuşum. Şöyle bir şey oldu: Ana ekrandaki verileri güncelleyen bir metod vardı. İçinde Task.Run. O task'ın içinden, başka bir UI elementini güncellemek için Dispatcher.Invoke çağırıyordum. O invoke'un içinde, yeni bir hesaplama yapılıyor ve onun için de yeni bir Task.Run başlatılıyordu. Şaka gibi ama, kodun %70'i BackgroundWorker, Task, callback ve dispatcher kontrolünden ibaret hale geldi.
C#:
private void Button_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
var data = GetHeavyData();
Dispatcher.Invoke(() =>
{
label1.Text = "İşleniyor...";
Task.Run(() => ProcessDataAsync(data));
});
});
}
Debug yapmak kabusa döndü. Bir hatayı takip ederken thread'ler arasında zıplayıp duruyorsun. Call stack öyle karışık ki, sorunun nerede olduğunu anlamak 10 dakikadan fazla sürüyor. StackOverflow'da bile "bunu nasıl düzgün yapacağım" diye ararken, aslında problemi kendimin yarattığını fark ettim. Her şeyi thread'e atmak, çözüm değilmiş. async/await ile doğru yapıyı kurmak, her yere çekiçle vurmaktan daha önemliymiş.
Nihayetinde kafamı toplayıp, iş mantığını (business logic) UI kodundan tamamen ayırmaya karar verdim. Tüm bu "ağır işler" için merkezi, yönetilebilir bir servis katmanı yazdım. UI tarafı sadece await ile bu servisleri çağırıyor ve sonucu alınca güncelliyor. Artık her buton click'inde yeni bir thread havuzu açılmıyor. Kod daha temiz, daha takip edilebilir oldu.
Peki ya siz? UI responsiveliği uğruna kodunuzu thread cehennemine çevirdiğiniz oldu mu? Bu işin temiz, sürdürülebilir yolu sizce nasıl olmalı? Mutlaka yorumlarda tartışalım.