Maç sonucu oluştuğunda simülasyonun veritabanını beklemesini istemeyebilirim. Üretici sonucu kuyruğa bırakır, bir tüketici sırayla işler. Channel bu ayrımı kurmak için kullanışlı, ancak “kuyruğa yazıldı” ile “kalıcı kaydedildi” arasındaki farkı baştan korumak gerekir.
İki tarafı olan bir akış
Arşivdeki maç simülasyonu ve oda akışlarından bağımsız bu .NET 10 konsol örneği dört elemanlık bounded channel kuruyor. Writer maç kimliklerini ekler, tek tüketici Reader üzerinden alır. Konsola saved:1 ve saved:2 yazılması örneğin gözlenebilir sonucu; burada gerçek veritabanı kaydı yok.
using System.Threading.Channels;
var queue=Channel.CreateBounded<int>(new BoundedChannelOptions(4){
SingleReader=true,FullMode=BoundedChannelFullMode.Wait
});
var consumer=Task.Run(async()=>{
await foreach(var matchId in queue.Reader.ReadAllAsync())
Console.WriteLine($"saved:{matchId}");
});
await queue.Writer.WriteAsync(1);
await queue.Writer.WriteAsync(2);
queue.Writer.Complete();
await consumer;
WriteAsync, kanal doluysa FullMode.Wait nedeniyle yer açılmasını bekleyebilir. Bu bekleme backpressure sağlar: üretici sınırsız hızla bellek büyütmek yerine tüketicinin hızını hisseder. Dört kapasitesi bir performans önerisi değil, küçük gösterimin sınırıdır. Gerçek sayı sonuç büyüklüğü, üretim hızı ve kabul edilebilir bekleme süresiyle ölçülür.
Tek okuyucu hangi sırayı sağlar?
Bu kod bir consumer görevi başlatıyor ve okuyucu her kaydı sırayla işliyor. SingleReader=true seçeneği kanal uygulamasına verilen bir kullanım taahhüdüdür; bütün uygulamayı veya başka sunucuları kilitlemez. Sonradan ikinci okuyucu eklemek bu sözleşmeyi yeniden değerlendirmeyi gerektirir.
Birden çok üreticinin eşzamanlı yazdığı durumda kuyruğa kabul sırası ile maçların iş alanındaki bitiş sırası farklı olabilir. İşlenen sonuçta MatchId, dünya/tur bilgisi ve gerekiyorsa sıra numarası bulunmalı. Yalnız “son gelen en yenidir” varsayımıyla skor tablosu güncellenmemeli.
Kanalın dolması ürün davranışıdır
Unbounded channel, üretici tüketiciyi uzun süre geçtiğinde belleği büyütebilir. Bounded yapı bu sınırı görünür kılar; fakat Wait seçimi simülasyonun da yavaşlayabilmesi demektir. Sonuç kaybetmek kabul edilmiyorsa drop modlarından birini seçmek teknik kolaylık olarak görülemez. Hangi veri kaybının kabul edilebilir olduğu iş kararıdır.
Örnekte yalnız iki öğe olduğu için kapasite baskısı gözlenmez. Bunu test etmek için yavaş bir tüketici ve kapasiteyi aşan kontrollü girdi gerekir. Amaç daha çok log üretmek değil, writer'ın ne zaman beklediğini ve uygulamanın bu bekleme sırasında ne yaptığını ölçmektir.
Kabul, işleme ve commit üç farklı an
WriteAsync tamamlandıysa öğe bellek içi kanala kabul edilmiştir. Henüz okuyucu almış veya veri commit edilmiş olmayabilir. Süreç o anda kapanırsa içerik kaybolabilir. Maç sonucunun kaybı kabul edilmiyorsa kalıcı kuyruk, outbox veya tekrar okunabilir bir sonuç kaydı gerekir. Channel yalnız süreç içi koordinasyon aracı olarak kalabilir.
Tüketici veritabanına yazdıktan sonra süreç hata verirse aynı sonuç tekrar gelebilir. MatchId ve sürüm gibi bir iş anahtarıyla idempotent kayıt kurmak burada da önemlidir. “Tek okuyucu var” demek yeniden denemede mükerrer etki oluşmayacağını kanıtlamaz.
Kapanışı ve hatayı iki uçla birlikte yönetmek
Writer.Complete yeni öğe gelmeyeceğini bildirir; consumer kalanları okuyup bittiğinde await consumer tamamlanır. Gerçek host kapanışında drain etmekle hemen iptal etmek farklı politikalardır. Tamamlama, timeout ve CancellationToken birlikte planlanmalı.
Tüketici hata verip durursa üreticiler dolu kanalda bekleyebilir. Hatanın writer tarafına da taşınması ve işin tekrar veya hata kuyruğu politikasına göre durması gerekir. Sessizce catch edip “saved” demek, kaydı başarıya dönüştürmez.
Testte sıra, kapasite baskısı, tüketici hatası, iptal ve kapanışta kalan öğeler sınanır. Kalıcı depo kullanılıyorsa son kontrol konsol mesajı değil, tekrar okunan maç sonucudur.
System.Threading.Channels modeli; BoundedChannelFullMode seçenekleri.