- Konu sahibi
- #1
İşlemi UI iş parçacığında, yani arayüzün düğme tıklamalarını ve pencere çizimini yöneten ana iş parçacığında çalıştırmayın. Dosya, ağ veya veritabanı işlemleri için asenkron API kullanıp
Bir olay işleyicisi içindeki kod,
Önce yavaş bölümün türünü belirleyin:
Bu ayrım önemlidir: Ağdan veri bekleyen bir kodu yalnızca
Aşağıdaki varsayımsal örnekte bir düğme, büyük bir metin dosyasını hesaplıyor.
Kodu düğmenin tıklama olayına bağlayıp uygulamayı çalıştırdığınızda beklenen sonuç, hesaplama sürerken etiketin “Hesaplanıyor...” yazması ve pencerenin tepki vermeye devam etmesidir. Hesaplama tamamlanınca etiket sonuçla değiştirilir. Bu sonuç, hesabın tamamlandığını gösterir; hesabın iş mantığının doğru olduğunu tek başına kanıtlamaz. Yanlış sonuç görürseniz artık UI donmasını değil,
Dosya veya ağ işlemi için kütüphanede asenkron metot varsa onu doğrudan bekleyin. Örneğin
Burada
Arka plan metodunun içinde doğrudan
9 ve üzeri WinForms uygulamalarında, gerçekten arka plandan ara sonuç göndermeniz gerekiyorsa
WPF kullanıyorsanız aynı ilke geçerlidir: hesaplamayı veya beklemeyi UI iş parçacığından ayırın, sonucu
İlk olarak olay işleyicisinde şu kalıpları arayın:
Kod değişikliğinden sonra sonucu şu şekilde yorumlayın: Pencere hareket ediyor ve düğmeler yanıt veriyorsa UI engeli büyük ölçüde kaldırılmıştır; hesaplama hâlâ uzun sürüyorsa bu normal olarak arka plan işinin devam ettiğini gösterir. Pencere yanıt veriyor fakat işlem sonunda hata alıyorsanız sorun artık iş parçacığı değil, istisnanın içeriğidir. Hata mesajını ve yığın izini geliştirici günlüğüne kaydedin; kullanıcıya hassas dosya yolları veya bağlantı bilgilerini doğrudan göstermeyin. Pencere hâlâ donuyorsa, ağır kodun gerçekten
await ile bekleyin; uzun süren hesaplama için işi Task.Run ile arka plana taşıyın. İş tamamlandıktan sonra sonucu UI iş parçacığında gösterin. Böylece arayüz işlem sırasında yeniden çizilebilir, kullanıcı girdilerini alabilir ve donmuş gibi görünmez.Donmanın kaynağını ayırın
Bir olay işleyicisi içindeki kod,
await veya arka plan görevi olmadan uzun süre çalışıyorsa UI iş parçacığı meşgul kalır. Bu sırada pencere yeniden çizilemez; düğmeler, taşıma işlemi ve kullanıcı girdileri yanıt vermiyor gibi görünür. await tek başına her kodu arka plana taşımaz. Özellikle CPU tüketen kendi metodunuzu ayrıca Task.Run içine almanız gerekir.Önce yavaş bölümün türünü belirleyin:
| İşlem türü | Uygun yaklaşım | Kaçınılması gereken yöntem |
|---|---|---|
| Dosya, ağ veya veritabanı beklemesi | Kütüphanenin asenkron metodunu await ile beklemek | UI iş parçacığında senkron bekleme |
| Büyük hesaplama, dönüştürme veya döngü | CPU işini Task.Run ile arka plana almak | Uzun hesaplamayı doğrudan olay işleyicisinde yapmak |
| Arka plandan kontrol güncelleme | Sonucu UI iş parçacığına taşımak | Kontrole doğrudan başka iş parçacığından erişmek |
Task.Run içine almak genellikle asıl çözüm değildir; mümkünse doğrudan asenkron ağ API'sini kullanın. Buna karşılık, milyonlarca kaydı işleyen veya görüntü dönüştüren CPU ağırlıklı bir metot için Task.Run uygundur.WinForms için uygulanabilir örnek
Aşağıdaki varsayımsal örnekte bir düğme, büyük bir metin dosyasını hesaplıyor.
Calculate metodu UI kontrollerine erişmediği için arka planda güvenle çalıştırılabilir. resultLabel ise yalnızca await sonrasında güncellenir.
C#:
private async void calculateButton_Click(object sender, EventArgs e)
{
calculateButton.Enabled = false;
resultLabel.Text = "Hesaplanıyor...";
try
{
// CPU ağırlıklı iş UI iş parçacığından ayrılır.
int result = await Task.Run(() => Calculate());
// await sonrasında WinForms bağlamında UI güncellemesi yapılır.
resultLabel.Text = $"Sonuç: {result}";
}
catch (OperationCanceledException)
{
resultLabel.Text = "İşlem iptal edildi.";
}
catch (Exception ex)
{
resultLabel.Text = "İşlem başarısız oldu.";
MessageBox.Show(ex.Message, "Hata");
}
finally
{
calculateButton.Enabled = true;
}
}
private static int Calculate()
{
int total = 0;
for (int i = 0; i < 100_000_000; i++)
total += i % 10;
return total;
}
Kodu düğmenin tıklama olayına bağlayıp uygulamayı çalıştırdığınızda beklenen sonuç, hesaplama sürerken etiketin “Hesaplanıyor...” yazması ve pencerenin tepki vermeye devam etmesidir. Hesaplama tamamlanınca etiket sonuçla değiştirilir. Bu sonuç, hesabın tamamlandığını gösterir; hesabın iş mantığının doğru olduğunu tek başına kanıtlamaz. Yanlış sonuç görürseniz artık UI donmasını değil,
Calculate içindeki veriyi ve algoritmayı denetlemelisiniz.Dosya veya ağ işlemi için kütüphanede asenkron metot varsa onu doğrudan bekleyin. Örneğin
ReadAllTextAsync, GetStringAsync veya sağlayıcının ...Async biçimindeki metotlarından biri kullanılabilir:
C#:
private async void loadButton_Click(object sender, EventArgs e)
{
loadButton.Enabled = false;
try
{
string text = await File.ReadAllTextAsync(filePathTextBox.Text);
contentTextBox.Text = text;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Dosya okunamadı");
}
finally
{
loadButton.Enabled = true;
}
}
Burada
Task.Run gerekli değildir; bekleme asenkron dosya API'si tarafından yönetilir. Asıl hata, ReadAllText gibi senkron bir çağrıyı UI iş parçacığında uzun dosya üzerinde çalıştırmak veya task.Wait() ya da task.Result ile sonucu senkron biçimde beklemektir.Arka plan işinden arayüzü güncellerken
Arka plan metodunun içinde doğrudan
label.Text, TextBox.Text veya başka bir kontrolü değiştirmeyin. WinForms kontrolleri onları oluşturan UI iş parçacığından erişilmelidir. Aksi durumda çapraz iş parçacığı hatası, yarış durumu veya yeni donmalar oluşabilir.9 ve üzeri WinForms uygulamalarında, gerçekten arka plandan ara sonuç göndermeniz gerekiyorsa
Control.InvokeAsync kullanılabilir. Daha sürümlerinde Invoke veya BeginInvoke gerekir; ancak Invoke çağıran iş parçacığını bekletebildiği için asenkron akışta kilitlenme riskine daha açıktır. En basit tasarım, arka plan metodunun yalnızca veri döndürmesi ve UI güncellemesinin await sonrasına bırakılmasıdır.WPF kullanıyorsanız aynı ilke geçerlidir: hesaplamayı veya beklemeyi UI iş parçacığından ayırın, sonucu
await sonrasında kontrole yazın. UI güncellemesi başka bir iş parçacığından zorunlu olarak yapılacaksa WPF'nin Dispatcher.InvokeAsync yaklaşımını kullanın. Task.Wait() ile dispatcher görevini senkron beklemeyin; bu, UI iş parçacığının kendi kuyruğundaki işi beklemesine ve deadlock oluşmasına yol açabilir.İşlem tamamlanmıyor veya uygulama yine donuyorsa
İlk olarak olay işleyicisinde şu kalıpları arayın:
.Result,.Wait()veya.GetAwaiter().GetResult()kullanımı- Uzun
forveyawhiledöngülerinin doğrudan UI olayında çalışması - Arka plan görevinden doğrudan kontrol özelliği değiştirilmesi
- Her ilerleme adımında UI'yi aşırı sık güncellemek
- Aynı düğmeye art arda basılarak birden fazla işlemin başlatılması
CancellationToken kabul eden bir metot tasarlayın ve form kapanırken veya kullanıcı İptal düğmesine bastığında iptal isteği gönderin. Döngü içinde periyodik olarak iptal kontrolü yapın. İşlem iptal edilmiyorsa pencere kapanırken görevin hâlâ kullandığı kaynakları hemen kapatmak yerine görevin tamamlanmasını veya kontrollü biçimde durmasını sağlayın.Kod değişikliğinden sonra sonucu şu şekilde yorumlayın: Pencere hareket ediyor ve düğmeler yanıt veriyorsa UI engeli büyük ölçüde kaldırılmıştır; hesaplama hâlâ uzun sürüyorsa bu normal olarak arka plan işinin devam ettiğini gösterir. Pencere yanıt veriyor fakat işlem sonunda hata alıyorsanız sorun artık iş parçacığı değil, istisnanın içeriğidir. Hata mesajını ve yığın izini geliştirici günlüğüne kaydedin; kullanıcıya hassas dosya yolları veya bağlantı bilgilerini doğrudan göstermeyin. Pencere hâlâ donuyorsa, ağır kodun gerçekten
await öncesinde mi çalıştığını, Task.Run içine alınan metodun başka bir yerde UI kontrolüne erişip erişmediğini ve senkron bekleme çağrısı kalıp kalmadığını kontrol edin.