C# yer kontrol istasyonu donuyor, arayüzü akıştan ayır
C# yer kontrol istasyonu donuyor ama telemetri akıyorsa sorun hatta değil, arayüze her pakette geçmende. Nedeni, çalışan kod ve hata mesajları burada.
Kendi yer kontrol istasyonunu C# ile yazdın. Bağlantı kuruluyor, sayılar akıyor, sonra pencere tutulup kalıyor. Fareyi gezdiriyorsun, başlık çubuğu bembeyaz oluyor. Telemetri hattında bir sorun yok; hat çalışıyor, çizen taraf yetişemiyor.
Buradaki tuzak, hattın beklediğinden çok daha hızlı olması. Gelen her pakette arayüzü rahatsız ediyorsun.
Baytlar arayüze nasıl ulaşıyor
Gelen bayt yığını önce çerçevelere ayrılır, sonra bir yere yazılır. Kararı tam bu yazma anında veriyorsun: değeri doğrudan ekrandaki kontrole mi yazacaksın, yoksa arada duran bir alana mı.
Aşağıdaki şekilde iki yolu aynı akışla yan yana gör. Üstte değer doğrudan ekrandaki kontrole yazılıyor, altta arada duran kilitli bir alana.
Aynı akış, arayüze geçiş sayısı farklı
Üstteki yolda okuma işini yapan iş parçacığı, her çerçeveden sonra arayüz iş parçacığına geçmek ve onun sırası gelene kadar beklemek zorunda kalır. Tek bir geçiş ucuzdur, saniyede binlercesi pencereyi kilitler.
Maliyeti çağrının sayısı belirliyor
Aynı telemetri akışını iki tasarımla tükettiğinde akış, süre ve çözülen çerçeve sayısı neredeyse aynı kalır. Değişen tek şey arayüze kaç kez geçildiği: 15 saniyede her pakette geçen tasarım 20 482 çerçeve için 20 482 kez geçer, 10 Hz tazeleme yapan tasarım 20 454 çerçeve için 150 kez.
Gerçek arayüz her geçişte hem bekler hem çizer. Çizim işi de eklenince mesaj kuyruğu birikmeye başlar ve pencere yanıt vermez hâle gelir.
Bir ayrıntı burada işleri karıştırıyor. Yer kontrol istasyonun otopilota kalp atışı basmıyor ve akış istemiyorsa, otopilot sana saniyede yalnızca birkaç paket gönderir; akışları açtığında bu sayı kat kat artar. Yani ilk denemende arayüz gayet akıcı görünür, akışları açtığın gün donar.
Okuma tarafını arayüzden ayır
Çözüm arayüze daha seyrek geçmek. Okuma iş parçacığı kilitli bir alana yazar ve hiçbir kontrole dokunmaz. Ayrı bir döngü sabit hızda uyanır, o alanın anlık kopyasını alır ve ekranı tek seferde tazeler.
sealed class TelemetriDurumu
{
readonly object _kilit = new();
long _paket; int _sonMesajId; DateTime _sonGorulme;
public void Uygula(int mesajId)
{
lock (_kilit) { _paket++; _sonMesajId = mesajId; _sonGorulme = DateTime.UtcNow; }
}
public (long paket, int mesajId, DateTime an) Oku()
{
lock (_kilit) { return (_paket, _sonMesajId, _sonGorulme); }
}
}
// Tazeleme dongusu: paket hizi ne olursa olsun sabit kalir.
var tazeleme = Task.Run(async () =>
{
var sonCizim = DateTime.UtcNow;
while (!dur.IsCancellationRequested)
{
await Task.Delay(50, CancellationToken.None);
if (sonCizim.AddMilliseconds(100) > DateTime.UtcNow) continue; // 10 Hz
sonCizim = DateTime.UtcNow;
var (paket, mesajId, an) = Durum.Oku();
// WinForms'ta bu satirin yerine tek bir BeginInvoke gelir.
ArayuzuCiz(paket, mesajId, an);
}
});Tazeleme hızı 10 Hz olduğunda paket hızı ne olursa olsun arayüze geçiş sayısı sabit kalır. Saniyede on tazeleme, akıcı görünen bir arayüz için yeter.
Bu tasarımı örnek bir programda 20 saniye çalıştırırsan şuna benzer bir çıktı alırsın:
cozulen cerceve : 29616
arayuze gecis : 192
gecis / cerceve : 0.65 %
son mesaj id : 11030Program yirmi dokuz binden fazla çerçeve çözer, arayüze yalnızca 192 kez geçer. Oran binde altı buçuk.
Örnek: Sürü uçuşunda aynı yer istasyonuna üç aracın telemetrisi geliyor. Her pakette arayüze geçen tasarımda geçiş sayısı araç sayısıyla birlikte büyür ve tek araçta idare eden pencere üç araçta donar. Kilitli alan ve 10 Hz tazeleme ile paket sayısı üçe katlansa da arayüz yine saniyede on kez çizer; araç sayısı penceredeki akıcılığı değiştirmez.
Hata Mesajları ve Anlamları
Bu arızayı çoğu zaman hiçbir hata almadan yaşarsın. Ama okuma iş parçacığından bir kontrole eriştiğin anda karşına çıkabilecek dizeler şunlar.
Cross-thread operation not valid: Control '...' accessed from a thread other than the thread it was created on.
Okuma iş parçacığından bir kontrolün penceresine erişmeye çalıştın. Bu denetimin açık olup olmadığı derleme biçimine değil, süreç başlarken hata ayıklayıcının bağlı olup olmadığına bakar. Yani hata ayıklayıcısız çalıştırdığın sürümde aynı yanlış kod sessizce geçer, arada bir tuhaf davranır.
Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
Form daha ekrana gelmeden arayüze geçiş denendi. Genellikle bağlantıyı kurucu metotta ya da form yüklenmeden başlatılan bir okuma döngüsünde çıkar.
The I/O operation has been aborted because of either a thread exit or an application request.
Okuma sürerken port kapatıldı. Kapatma sırasını tersine çevir: önce döngüyü durdur, bitmesini bekle, sonra portu kapat.
Probable I/O race condition detected while copying memory.
Aynı tampon iki iş parçacığından birden kullanılıyor. Okuma tamponunu tek bir iş parçacığına ait tut.
Devamı kursta
Bu yazı sorunun neye benzediğini ve tasarımın nasıl ayrılacağını anlatıyor. Kuyruk birikmesini kesen sayaç, kapanış sırası, kart bazlı farklar ve gerçek bir yer kontrol istasyonunun kaynak kodundaki karşılıkları Sıfırdan İleri Seviyeye C# eğitiminde adım adım işleniyor.
Softivation YKİ kütüphanesi: C# ile kendi yer kontrol istasyonunu yazarken MAVLink bağlantısı, Türkçe dokümantasyon ve hazır test arayüzüyle başla.
Bu yazı işinize yaradı mı?
Bu konuyu baştan sona öğrenin
Sezon: altı eğitimin hepsi 9 ay, ayda ₺790. Lise ve üniversite öğrencisine ayda ₺390, üye kodunuzla ilk ay ₺351. İstediğiniz ay bırakırsınız. Sezon'a başlayın · Öğrenciyim