C# 2026: Async Streams ve Cancellation ile Akış İşleme

IAsyncEnumerable, await foreach ve CancellationToken ile sayfalı verileri işleyin. .NET 10 üzerinde çalışan örneklerle iptal, hata ve kaynak yönetimini anlayın.

C# 2026: Async Streams ve Cancellation ile Akış İşleme — .NET 10 / C# 14
Sürüm notu: Örnekler 2026’da kararlı bir hedef olan .NET 10 / C# 14 üzerinde çalışır. IAsyncEnumerable<T> ve await foreach, C# 14 yeniliği değildir; C# 8 döneminden beri kullanılan araçlardır.

Binlerce siparişi sayfa sayfa okuyan bir uygulama düşünün. Bütün sayfaları bir listeye ekleyip işlemeyi en sona bırakmak kolaydır; fakat ilk sonucu görmek için tüm sorguların bitmesini bekler ve elde edilen veriyi bellekte tutarsınız.

Async streams, tüketicinin veriler hazır oldukça çalışmasını sağlar. Doğru kullanıldığında hem kontrol akışını hem de kaynak yönetimini anlaşılır hâle getirir. Ama tek başına paralellik, sınırsız performans veya otomatik iptal garantisi vermez.

Bu yazıda dış bir servise bağlanmadan çalışan bir sipariş akışı oluşturacağız. Ardından bu akışı nasıl iptal edeceğimizi ve hataları nerede yakalamamız gerektiğini göreceğiz.

1. Hangi dönüş tipini seçmelisiniz?

İhtiyaç Yaygın tercih
Tek bir asenkron sonuç Task<T>
Bütün sonuçlara birlikte ihtiyaç var Task<List<T>> veya benzer bir koleksiyon
Sonuçları hazır oldukça işlemek gerekiyor IAsyncEnumerable<T>

Hiçbiri her durumda diğerinden üstün değildir. Küçük bir veri kümesinde tüm listeyi almak daha basit olabilir. Sayfalı API, uzun süren rapor veya sürekli üretilen olay akışında ise IAsyncEnumerable<T> anlamlı bir seçenek hâline gelir.

Buradaki ayrım, sadece dönüş tipini değiştirmek değildir. Tüketici artık “bütün iş tamamlandı mı?” yerine “bir sonraki kayıt hazır mı?” sorusunu sorar.

2. Sayfaları sırayla üreten bir akış

.NET 10 konsol projesinde aşağıdaki OrderReader.cs dosyasını oluşturun:

using System.Runtime.CompilerServices;

namespace CSharp2026.AsyncStreams;

public sealed record Order(int Id, decimal Total);

public static class OrderReader
{
    public static async IAsyncEnumerable<Order> ReadAsync(
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        for (int page = 1; page <= 3; page++)
        {
            cancellationToken.ThrowIfCancellationRequested();
            var orders = await LoadPageAsync(page, cancellationToken);

            foreach (var order in orders)
            {
                cancellationToken.ThrowIfCancellationRequested();
                yield return order;
            }
        }
    }

    private static async Task<IReadOnlyList<Order>> LoadPageAsync(
        int page, CancellationToken cancellationToken)
    {
        // Ağ/veritabanı beklemesini taklit eder; dış sisteme bağlanmaz.
        await Task.Delay(10, cancellationToken);
        return [new Order(page * 10 + 1, 100m), new Order(page * 10 + 2, 200m)];
    }
}

Bu örnek üç sayfa üretir; her sayfada iki sipariş vardır. Task.Delay, ağ veya veritabanı beklemesini taklit eder. Gerçek bir dış sisteme istek yapılmaz.

Akışın önemli noktaları:

  • async ile yield return birlikte kullanılır.
  • Bir sayfa hazır olduğunda içindeki kayıtlar tüketiciye sırayla sunulur.
  • Sonraki sayfa, mevcut sayfanın kayıtları tüketildikten sonra istenir.
  • İptal hem bekleme işlemine iletilir hem de kayıtların arasında kontrol edilir.

Üretici, bütün siparişleri biriktiren ortak bir liste oluşturmaz. Yine de o anki sayfa bellektedir. Dolayısıyla “stream kullanınca hiç bellek tüketilmez” demek yanlış olur; bellek ihtiyacı sayfa boyutuna ve tüketicinin ne yaptığına bağlıdır.

3. await foreach ile tüketmek

Program.cs içindeki ana akış:

using CSharp2026.AsyncStreams;
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
int count = 0;
decimal total = 0;

try
{
    await foreach (var order in OrderReader.ReadAsync().WithCancellation(timeout.Token))
    {
        count++;
        total += order.Total;
        Console.WriteLine($"Sipariş {order.Id}: {order.Total}");
    }
    Console.WriteLine($"İşlenen: {count}; toplam: {total}");
}
catch (OperationCanceledException) when (timeout.IsCancellationRequested)
{
    Console.WriteLine("İşlem iptal edildi veya süre doldu.");
}

Normal koşullarda çıktı şöyle olur:

Sipariş 11: 100
Sipariş 12: 200
Sipariş 21: 100
Sipariş 22: 200
Sipariş 31: 100
Sipariş 32: 200
İşlenen: 6; toplam: 900

Örnekte beş saniyelik süre sınırı var. Veri üretimi çok kısa olduğu için normal çalışmada tüm kayıtlar işlenir. Gerçek bir ağ işlemi takıldığında veya kullanıcı iptal istediğinde aynı mekanizma akışın durdurulmasına yardım eder.

await foreach burada kayıtları sırayla işler. İçine await SaveAsync(order) koyarsanız bir siparişin kaydı tamamlanmadan bir sonrakine geçilmez. Bu, çoğu zaman istenen davranıştır. Paralellik gerekiyorsa onu ayrıca ve sınırlandırılmış biçimde tasarlamalısınız; her kayıt için kontrolsüz bir task başlatmak iyi bir çözüm değildir.

4. WithCancellation neden tek başına yeterli değil?

WithCancellation(token), token’ı enumerator oluşturulurken iletir. Üreticinin bu token’ı gerçekten kullanması gerekir.

Async iterator’daki [EnumeratorCancellation] niteliği, tüketiciden gelen token’ın iterator gövdesindeki parametreye ulaşmasını sağlar. Üretici hem parametreyle hem numaralandırma yoluyla token alıyorsa derleyici bunları uygun şekilde birleştirebilir.

Ancak bu bağlantı, iptali bütün işlemler için sihirli biçimde etkinleştirmez. Aşağıdaki adımlar hâlâ sizin sorumluluğunuzdadır:

  1. Token’ı HttpClient, veritabanı veya diğer asenkron çağrılara iletmek.
  2. Uzun CPU işlemlerinde uygun aralıklarla iptali kontrol etmek.
  3. İptali genel bir hata gibi yutup devam etmemek.

Örnekte Task.Delay(10, cancellationToken) bu zincirin bekleme tarafını, ThrowIfCancellationRequested() ise kayıtlar arasındaki kontrolü gösterir.

catch filtresine de dikkat edin. Kendi token’ımızın iptal edildiği durumda beklenen iptal mesajını gösteriyoruz. Farklı bir işlemden gelen her OperationCanceledException otomatik olarak “kullanıcı iptal etti” anlamına gelmez.

5. Hatalar ne zaman ortaya çıkar?

Bir async iterator metodu çağırmak, tüm veriyi hemen okumaz. Asıl iş numaralandırma başladığında yürür. Bu nedenle yalnızca ReadAsync() çağrısını try içine almak yeterli değildir; await foreach döngüsünü de kapsamalısınız.

Akış üçüncü sayfada hata verirse ilk iki sayfadaki kayıtlar zaten işlenmiş olabilir. Bu durum iş mantığınız açısından önemlidir. Rapor üretmek ile ödeme işlemi yapmak aynı hata stratejisine sahip olamaz.

Kalıcı yan etkiler varsa şu soruları baştan yanıtlayın:

  • Aynı kayıt yeniden işlenirse sonuç güvenli mi?
  • İşlem nereden devam edecek?
  • Kısmi başarı kullanıcıya nasıl bildirilecek?
  • Hangi hatalar tekrar denenebilir, hangileri kalıcıdır?

Bütün akışı baştan tekrar etmek, daha önce işlenen kayıtların etkilerini çoğaltabilir. Retry uygulamak tek başına idempotency sağlamaz. Kayıt kimliği, checkpoint veya işlemin niteliğine uygun başka bir mekanizma gerekebilir.

6. Kaynak yönetimi ve üretim ortamı

await foreach, döngü tamamlandığında, hata oluştuğunda veya break ile çıkıldığında enumerator’ı asenkron biçimde dispose eder. Bu, akışı durdurmanın kaynaklar açısından önemli bir parçasıdır; yine de üretici açtığı kaynakları doğru yönetmelidir.

Bir veritabanı reader’ı veya ağ kaynağı kullanıyorsanız yaşam süresini using / await using ile uygun kapsamda tutun. Akış henüz tüketilmeden gereken kaynağı kapatmak, daha sonra ortaya çıkan hatalara yol açabilir.

Örnekteki sabit üç sayfa yalnızca öğretim amaçlıdır. Gerçek bir serviste hasNextPage, cursor veya boş sayfa gibi bitiş koşullarını uygulayın. Tekrarlayan cursor’lara, tutarlı sıralamaya, zaman aşımına ve gerekirse toplam sayfa sınırına dikkat edin.

Son olarak tüketici bütün sonuçları yeniden bir listeye toplarsa, akışın bellek avantajı büyük ölçüde kaybolabilir. Aracı değil, uçtan uca veri işleme biçimini değerlendirin.

Sonuç: akış, iptal ve yan etkileri birlikte tasarlayın

Async streams’in en güçlü yanı, veriler hazır oldukça işlemeyi açık bir programlama modeliyle sunmasıdır. Sağlam bir çözüm için buna iptal zinciri, kaynak yaşam süresi ve kısmi başarı stratejisi de eklenmelidir.

Üç makalenin kod örneklerini indir — ZIP. AsyncStreams projesi kayıt sayısını, toplamı, sıralamayı, ilk kayıttan sonraki iptali ve önceden iptal edilmiş token davranışını altı doğrulamayla sınar.

Kaynaklar ve devamı

Bu yazı yapay zekâ desteğiyle hazırlanmıştır. İndirilebilir örnekler .NET SDK 10.0.401 üzerinde, C# 14 ayarı ve uyarıları hata kabul eden yapılandırmayla derlenip çalıştırılmıştır.