C# / .NET Sinyal Yapıları

Çağlar GÜL
Mar 16 · 5 min read

Sinyal yapıları ve sınıfları iş parçacıkları (thread) arası sinyalleşme amacı ile kullanılırlar. Sinyalleşme bir iş parçacığının bir diğerinden sinyal bekleme durumunda olması durumudur.

Olay bekleme handle’ları sinyalleşme yapılarının en basitidir. Aslında bu olayların C# Event ile bir alakaları yoktur. AutoResetEvent, ManualResetEvent ve CountDownEvent framwork’ün sahip olduğu ve bu amaçla kullanılması amacı ile hazırlanmış olaylardır. Şimdi bu olayları detaylı olarak inceleyelim.

AutoResetEvent

//Tanımlama1
var are1 = new AutoResetEvent(false)
//Tanımlama2
var are2 = new EventWaitHandle(false, EventResetMode.AutoReset);

Burada parametre olarak gönderdiğimiz false değeri sınıfın sadece bir örneğinin oluşturulmasını sağlamaktadır. Eğer true göndermiş olsaydık oluşturulduktan sonra AutoResetEvent örneğimiz otomatik olarak set edilecekti.

Şimdi de AutoResetEvent’in kullanımını örneklendirelim.

Çift Yönlü Haberleşme

AutoResetHandler’ı aynı zamanda iki iş parçacığının karşılıklı olarak iletişimi için de kullanabilirsiniz. Bunu yapabilmek için AutoResetHandler sınıfının iki farklı örneğini oluşturmanız gerekecektir. Bu örneklerden birisini bir iş parçacığını beklemek için diğerini ise diğer iş parçacığını yine beklemek için kullanacaklardır. Yine her iki iş parçacığınız da diğerinin beklemek için kullandığı örneği diğer iş parçacığını tekrar hareketlendirmek için kullanacaktır.

ManualResetEvent

AutoResetEvent karşılıklı olarak iki iş parçacığının iletişimi amacı ile kullanılabilirken ManualResetEvent çok benzemesine rağmen çalışma şeklini kale kapısına benzetebiliriz. Her ne kadar bu kapıda bekleyen nöbetçiler olmasa da kapı kapatıldığında hiç kimse bu kapıdan geçememekte, kapı açıldığında ise bekleyen herkes kapıdan geçebilmektedir. Bu örnek misali ManualResetEvent’in Reset metodunu çalıştırdığınızda bu kapı kapanmış olmaktadır ve WaitOne metodunu kullanan tüm iş parçacıkları beklemeye başlayacaklardır. Eğer Set metodunu kullanırsanız da beklemekte olan tüm iş parçacıkları harekete geçeceklerdir.

AutoResetEvent turnike gibi çalışır, her seferde tek kişi geçer ve kontrollüdür. Ancak MenualResetEvent yapısı bir geçite benzer ve kapaklar açılması halinde o anda orada olan tüm Thread’ler geçer farkları budur.

ManualResetEventSlim

.NET Framework 4.0 ile birlikte gelen ManualResetEventSlim yapısı EventWaitHandle olmasa da aynı amaçla kullanılmak üzere üretilmiştir. Hızın çok ön plana çıktığı durumlarda kullanımı çok önem kazanan bu yeni sınıf eskilerinden 50 kat daha hızlı çalışmaktadır. Bunun nedeni döngüleme (Spinning) kullanarak çalışıyor olmasıdır. Bu yeni sınıfın bir diğer yeniliği de CancellationToken kullanabiliyor olmasıdır. Böylece gerekli durumlarda bir Wait’i iptal edebilme şansı tanımaktadır.

CountdownEvent

CountdownEvent’in oluşturulma amacı diğerlerinden biraz daha farklıdır. Burada öncelikli amaç bir iş parçacığının birden fazla iş parçacığından sinyal beklemesini sağlamaktır. Örneğin bir iş parçacığınız bazı ön tanımlamalar yaptıktan sonra birden fazla iş parçacığını çalıştırdı ve hepsinin işi bittikten sonra kendi işine devam edecek. Böyle bir durumda rahatlıkla kullanabileceğiniz bir yapıdır CountdownEvent.

Kullanımına gelince, öncelikle bu sınıfın bir örneğini oluşturmanız ve sınıf yapıcısına kaç adet sinyal bekleyeceğini parametre olarak göndermeniz gerekmektedir. Daha sonra bekleyecek olan iş parçacığında diğer iş parçacıklarını çalıştırdıktan sonra Wait metodunu kullanarak ilk iş parçacığınızı beklemeye almalısınız. Sonrasında diğer iş parçacıklarınızda işleri bittikten sonra Signal metodunu çağırmanız gerekmektedir. Tüm iş parçacıkları Signal metodunu çalıştırdığında ilk iş parçacığınız tekrar harekete geçecek ve işini tamamlayabilecektir.

Ayrıca AddCount ve TryAddCount metodları ile beklenen sinyal sayısını değiştirebilirsiniz. Burada dikkat etmeniz gereken nokta sinyal sayısının 0 olmasına izin vermemektir. Böyle bir durumda hata üretilecektir. Zaten TryAddCount metodunun oluşturulma nedeni de budur.

ManualResetEventSlim ve CountdownEvent sınıfları başka sınıf ve metodların WaitHandle sınıfı bir nesne beklemeleri durumuna karşılık bir WaitHandle öz niteliğine sahiptirler.

WaitHandles

WaitHandles genel olarak ThreadPool sınıfında kullanılmaktadır. Bu yüzden kısaca bir ThreadPool sınfıının özelliklerinden bahsedelim.

Bu sınıfı kullanarak iş parçacıklarınızı bir havuzda toplayabilirsiniz. Programlarınızda yazdığınız iş parçacıklarının pek çoğu zamanının önemli sayılabilecek bir kısmını uyku modunda geçirirler. ThreadPool sınıfı iş parçacıklarınızın kontrolünü sisteme devrederek daha efetktif kullanılmasını sağlamaktadır.

Havuza alınan iş parçacıkları otomatik olarak arka planda çalışacak şekilde ayarlanmıştır. Dolayısıyla programınız kapatılırken bu iş parçacıkları otomatik olarak durdurulurlar.

İş parçacıklarınızı havuz kuyruğuna atabilirsiniz böylece bu kuyruktaki iş parçacıkları sırası geldiğinde bu sınıf tarafından çalıştırılırlar. Yalnız kuyruğa eklenmiş bir iş parçacığını iptal etmenin bir yolu mevcut değildir. Kodlarınızı yazarken bunu aklınızdan çıkarmamanızda fayda olacaktır. Bir iş parçacığını kuyruğa atabilmek için yapmanız gereken QueueUserWorkItem metodunu kullanmanız yeterli olacaktır. Kuyruk sisteminin çalışabilmesi için SetMaxThreads metodunu kullanarak maksimum eş zamanlı çalışacak iş parçacığı sayısını programınızın ihtiyacına göre belirlemeniz gerekmektedir. GetMaxThreads metodu ile de o andaki eş zamanlı çalışabilecek en fazla iş parçacığı sayısını alabilirsiniz.

Her iş parçacığının oluşturulması ve ilk kez çalıştırılması bir miktar zaman almaktadır. Siz Start metodunu kullanır kullanmaz devreye girememektedir. Bunun nedeni bir takım ön hazırlıklar yapılması CLR, İşletim sistemi ve işlemci ile ilgili düzenlemeler ve kayıtlar yapılması gerekmektedir. İşte siz çalışacak en az iş parçacığı sayısını belirlerseniz ThreadPool sınıfı bu sayıda iş parçacığını her an hazır bulunduracaktır. Böylece yeni bir iş parçacığı eklemek istediğinizde eğer o an çalışan iş parçacığı sayısı bu rakamdan az ise çok hızlı bir şekilde yeni iş parçacığınız çalışmaya başlayabilecektir. Yalnız bu rakamı sorumluluk sahibi bir programcı olarak belirlemeniz sistem kaynaklarının boşa harcanmasının önüne geçecektir.

ThreadPool sınıfının bir diğer yeteneği de iş parçacığını başlatmak için bir WaitHandle bekleyebilmektir. Bunun için ThreadPool.RegisterWaitForSingleObject metodunu kullanabilirsiniz. Bu metoda göndereceğiniz bir ManualResetEvent örneğine daha sonradan Set metodu ile sinyal gönderene kadar iş parçacığı çalıştırılmayacaktır.

Örnek bir uygulama yapalım:
Öncelikle bir ManualResetEvent örneği oluşturulur.

static ManualResetEvent tetikleyici = new ManualResetEvent (false);

Daha sonra kodunuzun içinde çalıştıracağınız RegisterWaitForSingleObject metodu ile iş parçacığı için hazırlıklar tamamlanır. 4. parametre olarak göreceğiniz -1 değeri bir zaman aşımı süresi tanımlamadığımızı ve sinyal gelene kadar bekleyeceği anlamına gelmektedir. Eğer burada bir değer girmiş olsaydık zaman aşımı süresi tanımlamış olurduk ve o süre tamamlandığında iş parçacığımız otomatik olarak çalıştırılacaktı. 5. parametredeki true değeri ise iş parçacığımızın sadece bir kere çalıştırılacağı anlamına gelmektedir. Eğer bu parametreyi false vermiş olsaydık her zaman aşımı gerçekleştiğinde tekrar tekrar çalıştırılacaktır.

RegisteredWaitHandle reg = ThreadPool.RegisterWaitForSingleObject(tetikleyici, IsciMetod, “Parametre olarak gidecek”, -1, true);

İş parçacığınızı çalıştırma vakti geldiğinde yapmanız gereken tetikleyici nesnesinin Set metodunu çalıştırmaktır. Siz bu metodu çalıştırana kadar iş parçacığı devreye alınmayacaktır.

tetikleyici.Set();

Son olarak ise bu nesne ile işiniz bittiğinde sistem kaynaklarını boşaltmak için Unregister metodunu kullanmayı unutmamalısınız.

reg.Unregister (tetikleyici);

Parametre olarak WaitHandle dizisi alan bu durağan metod tüm WaitHandle’lar sinyal alana kadar beklemeye devam edecektir. Ta ki dizideki tüm WaitHandle’lar sinyallendiklerinde bu metodu çağıran iş parçacığı çalışmasına devam edecektir. Bu metodun diğer üst yüklemeleri için Microsoft’un dökümanlarına bakabilirsiniz.

Bu metod da parametre olarak WaitHandle dizisi almaktadır. Bu metodun farkı ise dizideki herhangi bir WaitHandle sinyalllendiğinde bu metodu çağıran iş parçacığı çalışmasına devam edecektir. Bu metodun diğer üst yüklemeleri için Microsoft’un dökümanlarına bakabilirsiniz.

Bu metod ise iki adet WaitHandle almaktadır. Bunlardan birincisine sinyal gönderdikten sonra diğeri için WaitOne metodunu çalıştırır. Bu metodu kullanmanın faydası atomicity olarak birinci WaitHandle nesnesinin önce sinyallemesini garanti etmesidir. Bu metodun diğer üst yüklemeleri için Microsoft’un dökümanlarına bakabilirsiniz.

Barrier Sınıfı

Barrier sınıfı birden fazla iş parçacığının belirlenmiş bir zamanda buluşmasını sağlar. Framework 4.0 ile birlikte gelen bu sınıfı kullanarak belli bir zamanda iş parçacıklarınızı buluşturup sonra ikinci aşamaya geçmelerini sağlayabilirsiniz (Artık ikinci aşamanın ne olduğu size kalmış). Bu sınıf gerçekten çok hızlı ve efektif çalışır ve Wait, Pulse ve döngü kilitleme (spinlock) teknikleri kullanılarak hazırlanmıştır.

Bu sınıfı kullanabilmek için önce onu örneklemeniz ve bu örnekleme sırasında da kaç tane iş parçacığının bu örneği kullanacağını belirlemeniz gerekmektedir. Bu iş parçacıklarına Participant (katılımcı) denilmektedir ve katılımcı sayısını daha sonra AddParticipant ve RemoveParticipant metodları ile değiştirmeniz mümkündür. Daha sonra da tüm iş parçacıklarınızda randevu noktasına geldiklerinde SignalAndWait metodunu çalıştırırsınız. Bu metod çağırıldığında diğer iş parçacıklarının da bu metodu çağırmasını beklemeye başlar. En sonunda tüm katılımcılar bu metodu çağırdığında hepsi birden bir alt satırlarından çalışmalarına devam ederler.

Image for post
Image for post

Eğer isterseniz Barrier sınıfını örneklendirirken işi bittikten sonra çalıştırılmak üzere Action tanımlayabilirsiniz. Bu Action’ı tüm iş parçacıklarından sinyal aldıktan sonra nesneniz çalıştıracaktır. Yukarıdaki örnekte bulunan _barrier nesnesini oluşturduğumuz satırı aşağıdaki şekilde değiştirirsek;

static Barrier _barrier = new Barrier (3, barrier => Console.WriteLine());

Aşağıdaki örnek Burak Selim Şenyurt’tan aldığım örnektir.

Çağlar GÜL | Blog

Tecrübelerimi not aldığım bloğum 📄📌

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store