Threading in C# & Hesapkurdu

Mehmet Nuri Abacı
hesapkurdu-development
4 min readFeb 26, 2018

Threading

Threading, aynı ortamda aynı anda birden fazla işi yapmaya denir. Thread’ler ise bu işlerin her biridir. Thread’ler aynı anda çalıştığında işlemciye process’ler olarak gider ve sıraya alınır. Tek çekirdekli işlemcilerde birden fazla thread çalıştığı zaman sırayla threadlerin processlerini işleme alır ve bir thread diğer thread’in bitişini beklemeden processleri işlemcide işleme girer. Çok çekirdekli işlemcilerde ise farklı thread’ler farklı çekirdeklerde aynı anda çalışabilme durumuna sahiptir.

Şekil 1: Single thread vs Multi-thread

Thread neden kullanılıyor?

Thread’ler genel olarak, oyunlar, paralel çalışan task’ler, event handling gibi durumlarda kullanılmaktadır. Ayrıca çok çekirdekli işlemcilerde tam performans yararlanmak için de thread’ler kullanılır. İnsan üzerinden thread’leri açıklamak gerekirse, bir insan aynı anda konuşurken gözleri başka bir yere bakıp elleriyle ayrı işler yapabilmektedir. Bu tam olarak multithreading örneğidir.

Thread’lerin kullanıldığı bir başka alan ise server-client uygulamalar. Günümüzde popüler olarak Whatsapp, forum siteleri, online oyunlar bu mimariyi kullanmaktadırlar. Birden fazla kullanıcı birbirini beklemeden işlerini halletmek durumunda olduğundan her bir kullanıcının işlemleri ayrı threadlerde yönetilmektedir.

Bazı terimler

ThreadPool: İşletim sistemi seviyesinde threadlerin bir arada bulunduğu bir havuzdur. Çok fazla thread kullanan uygulamalar her bir thread işlemciye yük oluşturacağından thread ihtiyaçlarını bu havuzdan alarak çözmektedir. Örneğin IIS bu şekilde threadpool kullanmaktadır.

WorkerThread: ThreadPool’daki threadlerin her biri bir worker thread’dir

BackgroundWorker: Arkaplanda çalışan thread’lere verilen isimdir. Bir örnekle açıklamak gerekirse,

Bir windows masaüstü uygulaması düşünelim. Kullanıcı bir indirme işlemi başlatmak istiyor ancak bu işlem büyük dosya boyutlarında çok uzun süreceğinden backgroundworker kullanılmazsa kullanıcı programı indirme işlemi bitinceye kadar kullanamayacaktır. Bunun önüne geçmek için bir backgroundworker oluşturulup indirme işlemine burda devam etmesi ve kullanıcının da programı kesintiye uğramadan kullanması sağlanmaktadır.

Örneklerle C# Threading

C# uygulamalarında ana programın işlerinin yürüdüğü bir main thread bulunmaktadır. Her Thread objesi main thread’e paralel olarak çalışan bir worker thread yaratmaktadır.

Örnek Uygulama:

Şekil 2.1: C# Thread programlama örneği

Ekran çıktısı:

Şekil 2.2: Şekil 2.1’deki kodun ekran çıktısı
Şekil 3: Worker thread’ın main thread ile eş zamanlı çalışma grafiği

Join ve Sleep: Join() metotu thread’in işinin bitirmesini beklemek istediğimiz bir yer varsa kullanıyoruz. Örneğin bir download işlemi farklı bir worker thread ile yürütülürken, diğer bir worker thread içerisinde bu download işlemi bittikten sonra devam etmesi gerekebilir. Bu vb. durumlarda bu sıkça başvurulan metottur. Sleep() metotu ise thread’in çalışmasını belirli bir süre bekletmek için kullanılan metottur.

Örnek uygulama:

Şekil 4: C#’ta Thread.Join() kullanımı

Bu uygulamada “Go” metotu farklı bir worker thread ile çalıştırılıp işinin bitmesi beklenmiş ve ardından konsola metin basılmıştır.

Lambda expressions: Yeni bir thread lambda expression kullanılarak da oluşturulabilmektedir.

Örnek uygulama:

Şekil 5.1: Lambda expression ile Thread örneği

Ekran çıktısı:

Şekil 5.2: Şekil 5.1’deki kodun ekran çıktısı

Thread Safety: Thread’lerin ortaj nesneler üzerinde çalışması gerektiği durumlarda thread safety son derece önemli bir rol oynamaktadır. Bir nesneye aynı anda iki thread erişmek isterse bu durumda dead-lock olacak ve uygulama kitlenecektir. Bunun için thread’in kullandığı nesneyi lock’laması gerekmektedir.

Örnek Uygulama:

Şekil 6: Lock ile Thread Safety örneği

Bu ugulamada iki farklı worker thread aynı listeyi kullanmaktadır ve dead-lock oluşmaması için kullanılacak liste lock ile mühürlenmiştir.

Signaling with Event Wait Handles: WaitHandle’lar thread’ler arası haberleşmeyi sağlamak için kullanılmaktadırlar. Örneğin bir thread’in işe devam etmesi için diğer bir thread’in işinin bir kısmının bitmesi gerekiyorsa WaitHandle’lar kullınlmaktadır.

Örnek Uygulama:

Şekil 7.1: Thread’ler arası haberleşme, Event wait handles

Ekran çıktısı:

Şekil 7.2: Şekil 7.1’deki kodun ekran çıktısı ve akış grafiği

Biz Thread’leri nerede ve neden kullanıyoruz?

Aslında bizim uygulamamız gereği IIS her bir request’i bir thread olarak yönetiyor arkada. Ancak bizim uygulama katmanında gördüğümüz her biri ayrı birer “Web Request“. Thread olarak kullanmamız gereken yerlerde ise “Hangfire” kütüphanesini kullanıyoruz. Bunu kullandığımız “Enceladus” isimli projemizde trafik/kasko sigortası için asenkron teklif alma, header status automated check (sitede belirli url’lere ping atıp sağlıklı mı diye kontrol ettiğimiz sistem) ve hulusi projesi kapsamında her bir task için ayrı bir thread mevcut.

Thread’leri ilk olarak sigorta teklif sayfalarımızda kullanma ihtiyacı duyduk. Bunun temel sebebi ise her geçen gün artan dış servis entegrasyon sayımızın senkron çalışan teklif sayfamızı son derece yavaşlatması. Her bir şirketten teklif almak için ortalama bekleme süremiz 30–40 sn. arası, entegrasyon sayımız arttıkça sayfamız artık çok uzun sürede açılır hale gelmişti. Bu da bizi thread zorunlu olarak thread kullanımına itti bir bakıma. Ardından uzun süreli çalışan task’lerimizi sistemi daha az yorması ve yönetimi kolay olması bakımından thread’lere böldük. Thread kullanmaya başlama hikayemiz aslında bu şekilde.

Hangfire: Sınırlı bir kısmı ücretsiz olan kütüphane thread yönetimini kendi içerisinde başarılı bir şekilde yapabilmektedir. Batch olarak bir metot grubu eklenmek istenirse hangfire satın alınmalıdır. Tek çalışan metotlar için ücretsiz sürümü yeterli geliyor.

Biz de trafik sigortası için metotları batch halinde çalıştırmamız gerekiyor ancak pro sürümü son derece maliyetli. Peki bu sorunumuzu çözmek için nasıl bir yöntem geliştirdik? İnceleyelim.

Hangfire her bir job için bir job id üretiyor. Bu job id’leri bir listeye ekliyoruz. Bir yandan hangfire sırayla işeme alıyor job’ları. Ardından yazdığımız recursive metot’a bu listeyi parametre olarak geçiyoruz. Hangfire’ın sunduğu “ContinueWith” metotu ile bir job bittikten sonra bu işi yap diyebiliyoruz. Hangfire’ın bu metotunu kullanarak recursive yazdığımız metotta her bir job id için istek gönderiyoruz ve en son bütün job’lar tamamlandıktan sonra yapmamız gereken işi yapıyoruz.

Recursive metot:

Şekil 8.1: Hangfire batch işlemin bitmesini bekleyen recursive metot.

Job listesi:

Şekil 8.2: Şekil 8.1’de bitmesi beklenecek batch job listesi ve recursive metotun kullanılışı

--

--