Download as pdf or txt
Download as pdf or txt
You are on page 1of 9

20.11.

2017 C# le Mult thread Programlama - 1

Sitemize Hoşgeldiniz, Ziyaretçi! Oturum Aç Kayıt Ol Arama Üye Listesi Takvim

Genç Klavyeler › Masaüstü/Desktop Programlama › C#


C# ile Multithread Programlama - 1

Konuyu Değerlendir

C# ile Multithread Programlama - 1 Normal Mod | Çizgisel Mod

Beğen Arkadaşlarının neler beğend ğ n görmek ç n Kaydol.

24-06-2016, 10:34 (En son düzenleme: 29-01-2017 23:02 crispi.) Mesaj: #1

crispi
Mesajlar: 26
Üye
Katılım: 24-06-2016
Rep Puanı: 0

C# ile Multithread Programlama - 1

Merhabalar bu yazımda sizlere İngilizce tabiriyle Multithread programlamanın .NET Framework ve C# kullanılarak nasıl yapılabileceğini anlatmaya çalışacağım. Bu
konu aslında programcılık mesleğinin zor sayılabilecek bir parçası olduğundan elimden geldiğince geniş ve kapsamlı olarak incelemeye çalışacağım. 6 ana bölümden
oluşturmayı düşündüğüm bu yazılarımı sırası ile okumanızı ve her konuyu mümkün olduğunda detaylı inceleyerek ve kendi deneme kodlarınızı yazarak okumanızın
en doğrusu olacağını düşünüyorum. Eğer burada yazdığım sıralamada okumanızı gerçekleştirirseniz daha ilk yazımdan itibaren Multithread (çoklu iş parçacıklı yada
çok kanallı) programlar yazmaya başlayabileceğinizi umut ediyorum. Çok kanallı programlamayı 6 ana başlık altında topladığımı daha önce de yazmıştım. Şimdi bu
ana başlıkları listeleyerek bu ilk yazıma başlayalım.

1. Multithread Programlamanın Temelleri


2. Değişkenlerin Senkronizasyonu
3. İş parçacıklarının Senkronizasyonu
4. Timer sınıfları
5. Paralel Programlama
6. Eş zamanlı Koleksiyonlar

Bu yazımda 'thread' kelimesi için 'iş parçacığı', 'multithread' kelimesi için 'çok kanallı' tabirlerini kullanmayı uygun gördüğümü belirtmekte fayda olduğunu
düşünüyorum.

Multithread Programlamanın Temelleri


Öncelikle kavram olarak biraz threading konusunu incelemek doğru olacaktır. Thread kelimesi eğer sözlüğe bakarsanız pek çok alakasız anlamının yanı sıra 'en
küçük yürütme birimi' olarak da Türkçeye çevrilmektedir. Asıl anlamı iplik yada lif olan bu kelime bilgisayar teknolojisinde aynı anda birden fazla program
parçacığının çalıştırılması anlamında kullanılmaktadır Türkçe bilgisayar literatüründe iş parçacığı olarak adlandırılmaktadır.

Aslında siz çok kanallı bir program yazmasanız bile aslında en azından bir iş parçacığı çalıştırmaktasınızdır. Ana iş parçacığı (Main thread) adı verilen bu iş parçacığı
sizin tek iş parçacıklı (single threaded) yazdığınız tüm kodların çalıştığı iş parçacığıdır. Bunun yanı sıra siz farkında olmasanız bile bazı durumlarda CLR sizin yerinize
farklı iş parçacıkları oluşturarak bu iş parçacıklarında bazı işlemler yapmaktadır. Bu tip durumlara örnek olarak bazı nesnelerin sahip olduğu 'Begin....' şeklinde
başlayan metodlar gösterebiliriz. Aslında bu metodlar farklı bir iş parçacığı çalıştırarak orada işlemlerin yapılmasını sağlamaktadır. Bu sayede de sizin yazdığınız
diğer kodlar eşzamanlı olarak çalışmaya devam edebilmektedirler. Ayrıca bazı nesnelerin sahip oldukları '....Asynch' şeklindeki metodlarda ayrı birer iş parçacığı
çalıştırmaktadırlar.

İş parçacıkları aslında çalıştırılabilir bir program parçasıdır. Genellikle delege kullanarak tanımlamasını gerçekleştirdiğimiz birer parametreli ya da parametresiz
metoddurlar. Bu kodlar sadece belli bir görevi yerine getirdikten sonra işini bitirebildikleri gibi programınızın çalıştığı sürece sürekli çalışarak belli başlı görevleri
aralıksız yerine getiren program parçacıkları da olabilmektedirler. Tabi böyle bir durumda hesaba katmanız gereken olasıklıkların çok arttığını unutmamanız da
gerekmektedir. Örneğin bir işlemciyi en çok yoran işlemlerin başında döngüler gelmektedir. Tek işlemcili bir bilgisayarda eğer içinde işlem olmayan boş bir döngüyü
sonsuza kadar çalıştırırsanız CPU kullanımının çok yükseldiğini gözlemleyebilirsiniz. Tahmin edeceğiniz gibi programınız çalıştığı müddetçe çalışacak bir thread'in bir
döngü içinde olması gerekmektedir. Bu tarz bir program yazdığınızda bu döngünün CPU'u fazla meşgul etmemesini sağlamak zorundasınızdır. Bu noktada
yapılabilecek en temel işlem 'System.Threading' isim uzayında (namespace) bulunan 'Thread.Sleep' static metodunu kullanarak kodunuzun duraklamasını
sağlamaktır.

1 Thread.Sleep(5); // Burada kodunuz çalışmaya 5 milisaniye ara verecektir.

Bu aslında kodunuzun işlemciyi yormaması için yapılabilecek en basit ve profesyonel olmayan tekniktir diyebiliriz. Her nekadar bazı durumlarda yapılabilecek başka
bir şey olmamasına rağmen .Net Framework'ün bize sağladığı daha pek çok alternatif profesyonel teknikler ve yapılar bulunmaktadır. Fakat bu ilk yazımızda ağırlığı
temel tekniklere ve çok kanallı (multithread) programlamanın mantalitesine vermek istediğimden diğer tekniklerden şimdilik bahsetmeyeceğim.

Bu konuya biraz ara verip çoklu iş parçacıclı bir programın temelleri üzerine biraz daha konuşalım. Aslında işletim sistemleri ilk ortaya çıktığından bu yana mevcut

http://www.gencklavyeler.com/forum/konu-c- le-mult thread-programlama-1-25955.html 1/9


20.11.2017 C# le Mult thread Programlama - 1
olan bir tekniktir. Eğer öyle olmasaydı bilgisayarlar gerçekten efektif ve başarılı bir icat olamazlardı ve bizlerde başarılı programlar yazamazdık. Geçmişte Dos
ortamında program yazmış olanlar ne demek istediğimi hemen anlamışlardır. Dos işletim sisteminde program yazdığımız dönemde (özel programlar
kullanmadığımız takdirde) aynı anda bir program çalıştırabilirdik. Aksi bir şeyler yapabilmek için ise memory resident programlama teknikleri kullanmak durumunda
kalmaktaydık. Oysa bugün Windows yada diğer işletim sistemlerinde kolaylıkla aynı anda onlarca hatta yüzlerce program bilgisayarınızda çalışabilmektedir. Bunu
görmek için görev yöneticisini (task manager) açarak incelediğinizde Programlar sayfasında bir kaç program görünürken İşlemler sayfasına baktığınızda çok daha
fazla işlemin aynı anda çalışmakta olduğunu görebilirsiniz. Bu sayfadaki işlemler (process) aslında farklı farklı programlardır. Ve her işlem en azından bir iş parçacığı
kullanmaktadır. NET Framework ile yazılan bir programın sahip olabileceği maximum iş parçacığı sayısı belirlidir. Örneğin Framework 3.5 ta bu rakam 250 dir.
Framework 4.0 da ise bu rakamlar 32 bit işletim sisteminde 1023 iken 64 bit işletim sisteminde bu rakam 32767 olmaktadır.

Şekilde de görülebildiği üzere programınızın içinde yeni iş parçacıkları sizin tarafınızdan çalıştırılmaktadır. Ve tüm iş parçacıkları (ana iş parçacığı dahil)
sonlandığında programınız da sonlanmaktadır. Tüm iş parçacıkları derken dikkatinizi çekmek istediğim bir nokta ön plan olarak ayarlanmış iş parçacıklarınızın
sonlanması da da sizin sorumluluğunuzdadır. Eğer bir iş parçacığı sonlandırmayı unutursanız yada başarısız olursanız programınız sonsuza kadar çalışmaya devam
edecektir. Ekranda hiç bir şey görülmese bile görev yöneticisinden işlemler sayfasını incelediğinizde programınızın orada hala çalışıyor göründüğünü
gözlemleyebilirsiniz. Ama eğer bir iş parçacığını arka plan da çalışacak şekilde işaretlerseniz ana iş parçacığınız sonlandığında arka plan iş parçacıkları da otomatik
olarak işletim sistemi yada CLR tarafından durdurulacaktır.

Şimdi bir iş parçacığını nasıl oluşturabileceğinize geçmeden önce bazı önemli ek bilgiler vermek istiyorum. İş parçacıkları da processler gibi işletim sistemi
tarafından işlemcilere yada işlemci çekirdeklerine sırayla gönderilmektedirler. Yani, her bir iş parçacığının sıradaki kodunu (sizin yazdığınız kodlardan
bahsetmiyorum. Örneğin int x = x + y; işlemi işlemci tarafından 3 işlemde yapılabilmektedir) ilgili işlemciye belli bir mantığa bağlı olarak sıra ile göndermektedir.
Unutulmaması gereken bir diğer nokta da bir iş parçacığına ait kodların her zaman aynı işlemcide işlenmekte olduğudur. Yani sizin yazdığınız bir iş parçacığına ait
kodlar her zaman aynı işlemci yada işlemci çekirdeği tarafından işlenmektedir. Zira çalışma sırasında işlemci değiştirmek her ne kadar mümkün olsa da zorlu ve
zaman alan bir operasyon olmaktadır. Bu noktada aklınıza benim yazdığım iş parçacıklarının hangi işlemci yada çekirdekte işleneceğini nasıl belirleyebilirim sorusu
geldiğini düşünüyorum. Bu konuda endişe etmenize gerek yoktur. Zira bu dağılım tamamen işletim sisteminin sorumluluğudur ve sizin herhangi bir işlem
yapmanıza gerek bulunmamaktadır. Yani oluşturduğunuz tüm iş parçacıkları işletim sistemi tarafından tüm işlemci ve çekirdeklere otomatik olarak
dağıtılmaktadırlar.

Sanırım artık bir iş parçacığı oluşturma ve çok kanallı programlamanın engin sularına yelken açma vakti geldi. Yazımın bundan sonraki bölümlerine devam
edebilmek için temel programlama, c# ve .NET Framework hakkında yeterli temel bilgiye sahip olduğunuzu kabul ediyorum. Eğer bu konularda yeni iseniz bu
yazımın size faydalı olamayabileceğini düşünüyorum.

'System.Threading' isim uzayında bulunan Thread nesnesi kullanılarak yeni bir iş parçacığı oluşturulmaktadır. Aslında temel olarak yapılması gereken tek şey bu iş
parçacığını oluştururken hangi methodu çalıştıracağını belirtmek ve sonra da bu nesneye ait Start() metodunu çalıştırmaktır.

1 using System;
2 using System.Threading;
3
4 namespaceMultiThreadLessons
5 {
6 class Program
7 {
8 static void Main( string[] args )
9 {
10 Thread myThread = new Thread( Worker );
11 myThread.Start();
12 Thread.Sleep( 1500 );
13 Console.WriteLine( "Benim işim bitti..." );
14 }
15
16 static void Worker()
17 {
18 for (int i = 0; i < 10; i++) {
19 Console.WriteLine( "İşlem " + i );
20 Thread.Sleep( 1000 );
21 }
22 }
23 }
24 }

Yukarıdaki programda aslında ana iş parçacığı Main metodunun içinde çalışmakta ve bitmektedir ve yaptığı tek şey yeni bir iş parçacığı oluşturmak ve onu
çalıştırmaktan ibarettir. Bundan sonra yeni thread çalışmakta ve ekrana saniyede bir satır yazı yazmaktadır. Ana iş parçacığının işi 1.5 saniye sonra bitmesine
karşılık oluşturduğumuz iş parçacığı varsayılan olarak ön planda çalışmak üzere oluşturulduğundan ancak onu çalışması bittikten sonra programdan
çıkılabilmektedir. Programın ekran görüntüsü ise aşağıdaki gibi olacaktır.

http://www.gencklavyeler.com/forum/konu-c- le-mult thread-programlama-1-25955.html 2/9


20.11.2017 C# le Mult thread Programlama - 1

Burada da görülebileceği üzere ana iş parçacığının işi bittikten sonra yeni oluşturulan iş paraçcığı çalışmasına devam etmiş ve ancak o da işini tamamladıktan sonra
programdan çıkılabilmiştir. Eğer oluşturduğumuz iş parçacığını arka planda çalımak üzere ayarlamış olsaydık çalışma tamamen farklı olacaktı. Yukarıdaki örnekte
myThread.Start() satırından hemen öncesine aşağıdaki satırı ekleyip çalıştırdığınızda bu farkı sizde gözlemleyebilirsiniz.

1 myThread.IsBackground = true;

Bu kod eklendikten sonra programımızı çalıştırdığımızda aşağıdaki gibi bir ekranla karşılaşmaktayız:

Bunun sebebi arka planda çalışmak üzere ayarlanmış iş parçacıklarının ana iş parçacığı sonlandığında CLR tarafından otomatik olarak durduruluyor olmasıdır. Benim
tavsiyem çok önemli olmayan iş parçacıklarınızı arka zeminde çalışacak şekilde ayarlamanız ve bu sayede de programdan çıkışta onların durup durmadıklarını
kontrol etmekten kurtulmanızdır. Zira yeni programcılar genelde bu nokta da çok zorlanmaktadır ve programlarını sonlandırmak istediklerinde başarısız
olmaktadırlar. İş parçacıklarınzın durdurulması konusunu hem bu yazımda hemde ilerleyen yazılarımda daha detaylı olarak işleyeceğimden şu an için fazla detaya
girmiyorum.

Şimdi bir iş parçacığı nasıl oluşturulur konusuna konsantre olalım. Şimdi konsantrasyonunuzu şu iki satıra vermenizi istiyorum:

1 Thread myThread = new Thread( Worker );


2 myThread.Start();

Dikkat edeceğiniz üzere her iki durumda da eğer oluşturduğumuz metoda bir parametre göndermemiz gerekirse bunu bu iki teknikle de malesef yapamamaktayız.
Eğer böyle bir ihtiyacımız var ise bu durumda kullanabileceğimiz üç alternatifimiz bulunmaktadır. Bunlardan ilk ikisi ya ThreadStart sınıfını kullanmadan Start()
metodunda parametremizi göndermek veya ParameterizedThreadStart nesnesini kullanmaktır ki eğer bu iki tekniği tercih edersek metodumuza object tipinde bir
değişken gönderebiliriz. Şimdi programımızı biraz değiştirelim.

1 using System;
2 using System.Threading;
3
4 namespaceMultiThreadLessons
5 {
6 class Program
7 {
8 static void Main( string[] args )
9 {
10 Program prg = new Program();
11
12 // Aşağıdaki iki satırı da parametre gönderilen bir metodu thread ile çalıştırmak
13 // amacı ile kullanabilirsiniz.
14 //Thread myThread = new Thread( new ParameterizedThreadStart( prg.Worker ) );
15 Thread myThread = new Thread( prg.Worker );
16
17 myThread.Start( 5 );
18 Thread.Sleep( 1500 );
19
20 Console.WriteLine( "Benim işim bitti..." );
21 }
22
23 void Worker( object startPos )
24 {
25 for (int i = (int)startPos; i < 10; i++) {
26 Console.WriteLine( "İşlem " + i );
27 Thread.Sleep( 1000 );
28 }
29 }
30 }
31 }

Yukarıdaki programı her iki şekilde de (ParameterizedThreadStart kullanarak ya da sadece method'u parametre olarak göndererek) çalıştırdığımızda sonuç
değişmemektedir. Dikkat edeceğiniz üzere metodumuza göndermek istediğimiz değişkeni myThread nesnesinin Start() metoduna parametre olarak vererek kolayca
gönderebiliyoruz. Bu programı çalıştırdığımızda ise karşımıza aşağıdaki gibi bir ekran gelecektir.

http://www.gencklavyeler.com/forum/konu-c- le-mult thread-programlama-1-25955.html 3/9


20.11.2017 C# le Mult thread Programlama - 1

Bu iki tekniğin zayıf noktaları metodumuza sadece bir değişken gönderebilmemiz ve bu nesnenin tipinin object olmasıdır. Gerçi biz programcılar olarak bu
noktadaki eksiklikleri c#'ın bize sağlamış olduğu casting, boxing ve unboxing özelliklerini kullanarak çözümler ürettik ama sonuç olarak bu tip işlemlerin (özellikle
boxing ve unboxing) zaman alan işlemler olması nedeni ile de hiç bir zaman tam tatmin olamamıştık. Aşağıdaki örneği inceleyebilirsiniz.

1 myThread.Start( (object)( new object[] { id, adi, soyadi }));

Yukarıdaki satır object tipinde tek bir nesne alan metoda birden fazla nesne gönderimine bir örnektir. Metodumuzun içinde ise yapmamız gereken:

1 public void worker( object gelen )


2 {
3 int pos = (int)((object[])gelen)[0];
4 String adi = (String)((object[])gelen)[1];
5 String soyadi = (String)((object[])gelen)[2];
6 // buradan sonrasında bu yerel değişkenleri kullanarak kendi kodlarınızı rahatlıkla
7 // yazabilirsiniz.
8 }

Bugün artık lambda expression ve isimsiz methodlar kullanarak bu işlemleri daha rahat yapabilir hale gelmiş bulunmaktayız. Yukarıda bahsetmediğim üçüncü
tekniğin sırrı işte burada saklıdır. Metodumuzu doğrudan çağıran bir isimsiz metod yazarak ta aynı işi daha kolay bir şekilde yapabilmekteyiz. Yukarıdaki
programımızda ilgili satırları aşağıdaki gibi değiştirmeniz durumunda programın rahatlıkla çalışabildiğini görebilirsiniz.

1 Thread myThread = new Thread( () => prg.Worker(5) );


2 myThread.Start();

Üstelik worker metodumuzun parametresinin tipini de rahatlıkla object yerine int olarak belirleyebiliriz. Hatta istersek 10 parametre alan bir metod kullanmamızda
bile kesinlikle bir sakınca bulunmamaktadır. Programımızın son halini ise aşağıda bulabilirsiniz.

1 using System;
2 using System.Threading;
3
4 namespaceMultiThreadLessons
5 {
6 class Program
7 {
8 static void Main( string[] args )
9 {
10 Program prg = new Program();
11
12 Thread myThread = new Thread( () => prg.Worker( 5 ) );
13
14 myThread.Start();
15 Thread.Sleep( 1500 );
16
17 Console.WriteLine( "Benim işim bitti..." );
18 }
19
20 void Worker( int startPos)
21 {
22 for (int i = startPos; i < 10; i++) {
23 Console.WriteLine( "İşlem " + i );
24 Thread.Sleep( 1000 );
25 }
26 }
27 }
28 }

Yukarıdaki programlarda da gördüğünüz üzere yeni bir iş parçacığı oluşturmak ve çalıştırmak aslında iki satırlık bir işlemdir ve çok basittir. Evet gerçekten çok basit
gibi görünse de aslında proplem çalışan metodunuzun içinde ve ortak değişkenlerde ortaya çıkmaktadır. Aynı anda ortak bir değişkende değişiklik yapılması
durumunda hatta bu değişken basit bir integer değer bile olsa sorunlar ortaya çıkabilmektedir. Yada bir deadlock ile karşı karşıya kalmanız durumu da ayrıca büyük
bir problemdir. Deadlock denilen olay genelde iki farklı iş parçacığının birbirini beklemesi ve bu yüzden de hiç bir zaman işlerini yapamamaları durumunda karşımıza
çıkmaktadır. Sanırım bu durum bir çok kanallı programda yapılabilecek en kötü hatalardan biridir. Hele bir de yapılan testlerde karşılaşılmaması ama müşterinin
karşısına çıkması durumu biz programcıları gerçekten çok zor durumlarda bırakabilir.

Aslında internette gezdiğinizde bu konularla ilgili yazılar bulabilmektesiniz ama bu yazılanların bir kısmı maalesef sizleri yanlış yönlendirebilmektedir. Mesela 'atomic'
olarak yapılabilen işlemlerde herhangi bir kilitleme operasyonuna gerek olmadığı ve bu işin bir iş parçacığında gerçekleştirilmesi durumunda diğer iş parçacıklarında
yeni değeri anında görmeye başlayacağı gibi temelde doğru gibi görünüp gerçekte yanlış olan bilgilerle karşılaşabilinmektedir. Unutmayın ki bir işlem 'atomic' bile
olsa (yani 32 bit bir OS'de 32 bit bir değişken ile ilgili işlem yaptığınızda yada 64 bit bir OS'de ise 32 bit yada 64 bit bir değişkende işlem yaptığınızda işlemcinin bu
işlemi tek seferde bitirebilmesi durumu) CPU bazında birden fazla işlem yapılması gerekebilmektedir. Mesela 'i++' işlemi bir seferde kesinlikle halledilememektedir.
Önce i değeri okunmalı sonra o i değeri bir arttırılmalı ve sonra da tekrar i değişkenine yüklenmelidir. İşte bu işlemlerin orta yerinde diğer bir iş parçacığınız aynı
değişkende bir işlem yaparsa sonuç beklendiği gibi olamamaktadır.

Ortak değişkenlerde bir işlem yapmanız gerektiğinde yapılabilecek en basit işlem lock komutunu kullanmaktır. Kullanımı çok basit olan bu komut ile kodunuzun bir
kısmını kilitlersiniz. Bu kilit işlemi için ise referans tipi bir nesne kullanmanız gerekmektedir. Lock komutu bu nesneyi kilitleyerek erişimi engellemektedir. Aynı

http://www.gencklavyeler.com/forum/konu-c- le-mult thread-programlama-1-25955.html 4/9


20.11.2017 C# le Mult thread Programlama - 1
nesneyi aynı anda diğer bir iş parçacığı kilitleyemeyeceği için kilitlediğiniz kodlar güvenle çalışabilmektedirler. Aşağıda lock komutunun söz dizimini bulabilirsiniz.

1 lock ( kilitlenecekNesne ) {
2 // kodlarınızı buraya güvenle yazabilirsiniz.
3 }

Bu konutu kullanırken dikkat etmeniz ve bilmeniz gereken bazı noktalar vardır. Öncelikle çok hızlı çalışan bir kilitleme mekanizması olmasına rağmen kullanırken
çok dikkat etmeniz ve gereksiz kullanımlardan kaçınmanız programınızın performansı açısından faydalı olacaktır. Kilitleme için kullanılacak nesnenin ise dikkatle
seçilmesi ve doğru yerde doğru nesnenin kilitlenmesi de ayrıca çok önemlidir. Kilit altındaki kodlarınızın mümkün olduğunca az olması ve işini kısa zamanda
bitirmesi de ayrıca programınıza performans kazandıracak bir diğer unsurdur.

Kilitlenecek nesne konusunda genellikle yerel nesneler kullanılması tavsiye edilmektedir. Özellikle readonly local nesneler kullanmalısınız. Yalnız dikkat edin tek bir
nesne oluşturup sonra tüm kilitlerinizde onu kullanırsanız da bu sefer birbirleri ile aslında hiç ilgisi olmayan kod parçalarınız da birbirini beklemeye başlayacaktır. Bu
durum ise performans konusunda büyük sıkıntılarla karşılaşmanıza neden olabilecektir. Genel olarak ortak değişkenlerin kilitleme amacı ile kullanılması pek tavsiye
edilmemekte ise de ben dönem dönem kilidin amacını anlamayı çok kolaylaştırdığından bu tekniği kullanabilmekteyim. Örneğin bir listeye yeni bir eleman
ekleyeceğim zaman sadece tek satırlık bir kilit kullanıp kilit nesnesi olarak ta listenin kendisini verebilmekteyim*. Size bu konuda verebileceğim bir diğer ipucu ise
döndülerle ve linq ile alakalıdır. Eğer içinde çok sayıda değişken olan bir listede arama yapacaksanız yani bu listenin içindeki elemanları teker teker dolaşıp
kıyaslama yaparak içlerinden bir kısmını veya bir tanesini ayıklamaya çalışacaksanız, bu durumda kontroller için kullanacağınız kodun zorluk derecesine de bağlı
olarak tüm işlemi kilitlemek diğer thread'leri olumsuz etkileyebilecektir. Böyle bir durumda ise ToArray() metodunu kullanarak listenizi yeni bir geçici değişkene
atamanızı ve işlemlerinizi bu geçici değişken ile yapmanızı tavsiye edebilirim. Böylece sadece ToArray() satırını kilitleyerek güvenliği sağlamış olursunuz. Daha sonra
yapacağınız işlem bir saat bile sürecek olsa diğer threadleriniz bundan hiç etkilenmeksizin çalışmalarında devam edebileceklerdir.

*Burada kilit objesi olarak kesinlikle içinde bulunduğunuz nesneyi kullanmamalısınız. Yani lock(this) şeklinde bir yazım kesinlikle yanlıştır ve bu kullanım çok kanallı
program yazarken en çok karşılaşacağımız problemlerden biri olan gerçekleşemeyen kilit (deadlock) olarak Türkçeleştirdiğim problemi ile karşılaşma riskini çok
yükseltecektir. Bunun yerine aşağıdaki örnekte göstereceğim gibi ayrı bir kilit nesnesi kullanmak kesinlikle en kesin ve garantili yoldur.

Şimdi güvenli olmayan bir thread örneğini inceleyelim:

1 static class NormalSınıf


2 {
3 static int x;
4 static void Arttir() { x++; }
5 static void Yukle() { x = 123; }
6 }

Yukarıdaki sınıf tek iş parçacıklı bir uygulamada kesinlikle sorunsuz olarak çalışabilmesine rağmen çok kanallı bir uygulamada kesinlikle güvenli değildir. Zira eğer
Arttır yada Azalt metotlarının aynı anda birden fazla thread farklı işlemci yada çekirdeklerde çalıştırırsa bu durum sizin beklediğinizden farklı sonuçlarla
karşılaşmanıza neden olabilecektir. Bu nedenle yapılması gereken bu işlemleri kilit altına almaktır.

Aynı sınıfın güvenli versiyonuna bakalım:

1 static class GuvenliSınıf


2 {
3 static readonly object kilit = new object();
4 static int x;
5
6 static void Arttir() { lock (kilit) x++; }
7 static void Yukle() { lock (kilit) x = 123; }
8 }

Bu sınıf ise çok kanallı bir uygulamada kesinlikle güvenli çalışacaktır. Zira aynı anda birden fazla iş parçacığının kilit nesnesini kilit altına alamayacağı için
işlemlerinizin birbirleri ile çakışma tehlikesi bertaraf edilmiş olacaktır. Bu sayede de programınızı güvenle yazabilirsiniz. Bu tarz işlemlere bloklama adı verilmektedir.
Unutmayın ki bloklama işlemi sadece değer atamada değil değer okumada da önemlidir. Senkronizasyon adı verilen bu durum bir iş parçacığının bir değişkenin
değerinde bir değişiklik yaptığı anda diğer bir iş parçacığının bu değeri görebilmesidir.

Senkronizasyon başlı başına bir konu olduğu için ilerleyen zamanda o konuda da ayrı bir blog yazmayı planlıyorum.

Lock komutunun dezavantajı kilit gerçekleşene kadar iş parçacığını bekleme moduna almasıdır. Framework kapsam değiştime adı verilen bu tekniğe alternatif
yapılar sunmaktadır. Ama bu yazımda temel konulara ağırlık verdiğimden diğer yapıları bir sonraki yazımda işleyeceğim.

İleri Programlama İpucu : Eğer bir programınızda bir iş parçacığınız bir listeyi dolduruyor ve diğeri ise bu listeden veri okuyup işliyor ise her iki iş parçacığınızda
sürekli kilit işlemi yapıp beklemek yerine listeyi dolduran iş parçacığınıza ek bir metod yazarak daha hızlı çalışan bir program yazabilirsiniz. Aşağıdaki örneğimizi
inceleyelim;

1 static class Ipucu


2 {
3 static readonly object kilit = new object();
4 Queue<String> kuyruk = new Queue<String>();
5 Thread doldurucu, isleyici;
6
7 private void Doldur()
8 {
9 while (true) {
10 lock(kilit)
11 Kuyruk.Add(new String());
12 }
13 }
14 private void oku()
15 {
16 lock(kilit) {
17 Queue<String> tmp = kuyruk;

http://www.gencklavyeler.com/forum/konu-c- le-mult thread-programlama-1-25955.html 5/9


20.11.2017 C# le Mult thread Programlama - 1
18 Kuyruk = new Queue<String>();
19 return tmp;
20 }
21 }
22 private void isle()
23 {
24 Queue<String> kuyruk2 = new Queue<String>();
25 while (true) {
26 if( kuyruk2.Count == 0 )
27 Kuyruk2 = oku();
28
29 String veri = kuyruk2.Dequeue();
30
31 // Burada gerekli işlemleri yapabilirsiniz.
32 }
33 }
34
35 public void start()
36 {
37 doldurucu = new Thread( () = doldur() );
38 doldurucu.IsBackground = true;
39 doldurucu.Start();
40 isleyici = new Thread( () = isle() );
41 isleyici.IsBackground = true;
42 isleyici.Start();
43 }
44 }

Bu kod hakkında konumuzun dışına çıkmamak için fazla bir şey yazmayacağım, ama inanıyorum ki biraz incelediğinizde hepiniz bu kodun programımıza nasıl bir
katkıda bulunacağını göreceksiniz.

Thread.Sleep()

Daha önce de belirttiğim üzere Thread sınıfının statik metodu olan Sleep() metodunu kullanarak geçici olarak iş parçacıklarınız dondurabilirsiniz. Sleep aşamasında
iş parçacığınız kesinlikle CPU'yu meşgul etmemektedir. Bu nedenle sürekli olarak çalışması gereken metodlarınızda kullanmanızı tavsiye etmekteyim. Eğer hız
sorunundan endişe ediyorsanız ve bu dinlendirme kodunuza zarar verir endişesi taşıyorsanız Thread.Sleep(0) şeklinde bir kullanım ile işlemci için bir rahatlama
zamanı oluşturabilirsiniz. Testler yaptığınızda sizde göreceksiniz ki Thread.Sleep(5) (5 milisaniye duraklama) şeklinde bir kullanım dahi işlemcide büyük bir
rahatlama sağlayacaktır.

Gerçi sleep metodu ile uyutmak gerçek bir çok kanallı uygulamanın kullanacağı bir teknik olmamalıdır. Bundan kaçınabilmek için de bazı alternatif teknikler ve
sınıflar NET Framework'te mevcuttur ve bu konu ile ilgili de bir yazımı ilerleyen günlerde yayınlayacağım.

Join

Zaman zaman bulunduğumuz iş parçacığının diğer iş parçacığının sonlanmasını beklemesi gerekebilmektedir ve diğer iş parçacığı sonlandıktan sonra kendi işine
devam etmesi gerekebilmektedir. İşte join metodu bu ve benzeri diğer durumlarda kullanabileceğimiz bir metoddur. Çalıştırıldığında aktif iş parçacığı diğeri ile
birleşmekte ve diğer iş parçacığı sonlandıktan sonra bir alt satırdan çalışmasına devam edebilmektedir.

Bir diğer durum ise programımızı kapatırken karşımıza çıkmaktadır. Daha önce de bahsettiğim üzere ön planda çalışmak üzere ayarlanmış bir iş parçacığı
sonlanmadan programımız sonlanamamaktadır. Bu nedenle de programımızı kapatırken tüm ön plan iş parçacıklarınızın durduğundan emin olmamız gerekmektedir.
Bu durumda ise yapılması gereken ilgili iş parçacığına durma emrini verdikten sonra o durana kadar beklemektir. Bu iş için her ne kadar ThreadState nesnesini
kullanabiliyor olsak ta sürekli bir döngü içinde bu tip bir kontrol yapmak çok da doğru olmayacağından durma emrini verdikten sonra join metodu ile o iş
parçacığına bağlanarak onun sonlanmasını herhangi bir ek işlem yapmaksızın bekleyebiliriz. Herhangi bir şekilde de kapanmaması durumunda ise Visual Studio'nun
debugger'ı ile sorunun yerini belirlememiz daha da kolay olabilecektir.

Şimdi daha önce yazdığımız programımızı biraz değiştirerek tekrar gözden geçirelim:

1 using System;
2 using System.Threading;
3
4 namespaceMultiThreadLessons
5 {
6 class Program
7 {
8 static void Main( string[] args )
9 {
10 Thread myThread = new Thread( Worker );
11 myThread.IsBackground = true;
12 myThread.Start();
13 myThread.Join();
14 Console.WriteLine( "Benim işim bitti..." );
15 }
16
17 static void Worker()
18 {
19 for (int i = 0; i < 10; i++) {
20 Console.WriteLine( "İşlem " + i );
21 Thread.Sleep( 1000 );
22 }
23 }
24 }
25 }

Buradaki püf nokta iş parçacığımızı arka planda çalışması işin ayarlamış olmamızdır. Bu nedenle normal şartlarda ekranda sadece 'Benim işim bitti...' yazısını
görebilmemiz gerekmektedir. Zira programımız sonlandığında arka plan iş parçacıkları da sonlanacağından belkide worker metodumuz hiç çalışamadan program
sonlanacaktır. Bunu önlemek için bu programımızda Join metodunu kullanmaktayız. Bu sayede çalıştırdığımız iş parçacığı sonlandıktan sonra ana iş parçacığımız
çalışmasına devam etmekte ve kendi işini de bitirdikten sonra programımız sonlanmaktadır. Programımızın sonucunu aşağıda görebilirsiniz.

http://www.gencklavyeler.com/forum/konu-c- le-mult thread-programlama-1-25955.html 6/9


20.11.2017 C# le Mult thread Programlama - 1

Thread.Abort()

Çalışan iş parçacıklarınızı durdurmanın bir diğer yolu da Thread sınıfının Abort metodunu çalıştırmaktır. Bir iş parçacığını bu şekilde durdurmak her ne kadar çok
faydalı değilse de bazı durumlarda son alternatif olarak başvurulabilecek bir çözümdür. İş parçacığınızın Abort metodunu çağırdığınızda zorla kapatılacaktır.
Herhangi bir parametre almayan bu metodu çalıştırdığınızda iş parçacığınızın durumu öncelikle AbortRequested olarak değişecektir. Bir süre sonra da iş parçacığı
durdurularak durumu (ThreadState) Aborted olarak değiştirilecektir.

Abort metodunu kullanarak bir iş parçacığını durdurduğunuzda ThreadAbortException hatası ile karşılaşırsınız. Bu nedenle bu exception'ı mutlaka kontrol altında
tutmanız gerekmektedir. Bunun için ise yapmanız gereken bildiğiniz üzere try..catch kullanmaktır. Aşağıda bu kontrolüde içeren örnek bir iş parçacığı döngüsü
bulabilirsiniz.

1 using System;
2 using System.Threading;
3
4 namespaceMultiThreadLessons
5 {
6 class Program
7 {
8 static void Main( string[] args )
9 {
10 Thread myThread = new Thread( Worker );
11 myThread.Start();
12 Thread.Sleep( 2000 );
13 Console.WriteLine( myThread.ThreadState );
14
15 myThread.Abort();
16 Console.WriteLine( myThread.ThreadState );
17
18 myThread.Join();
19 Console.WriteLine( myThread.ThreadState );
20 }
21
22 static void Worker()
23 {
24 try {
25 while (true)
26 try {
27
28 // Kodlarınızı buraya yazabilirsiniz...
29
30 Thread.Sleep(0);
31
32 } catch(ThreadAbortException) {
33 Console.WriteLine( "Thread zorla kapatılmıştır...(2)" );
34 break;
35 } catch (Exception ex) {
36 Console.WriteLine( "Beklenmeyen Hata: " + ex.Message );
37 }
38 } catch(ThreadAbortException) {
39 Console.WriteLine( "Thread zorla kapatılmıştır...(1)" );
40 }
41
42 // Burada son işlemlerinizi gerçekleştirebilirsiniz.
43 }
44 }
45 }

Bu programda iki adet try..cacth bloğu kullanmamızın nedeni eğer döngünün içinde iken bir hata ile karşılaşırsak döngüden çıkmadan önce ne yapacağımıza karar
verebilmemizi sağlamaktır. Eğer dögünün içinde iken iş parçacığımız abort edilirse iş parçacığını sonlandırmak amacı ile break komutu ile döngüden çıkmamız
yeterli olacaktır. Çıkarken de yapmak istediğimiz son düzenlemeler yada işlemlerimiz var ise onları da tamamlama şansına sahip olabiliriz.

Abort metodu ile bir iş parçacığını sonlandırmak daha önce de bahsettiğim üzere sadece zorunlu durumlarda başvurulması gereken bir tekniktir. Bu nedenle tüm iş
parçacıklarınıza mutlaka bir sonlandırma mantığı yerleştirmeniz gerekecektir. Yukarıdaki örneğimizi ele aldığımızda isRunning adında boolean bir değişkenimiz
olduğunu varsayalım. Bunun yanı sıra da while(true) döngümüzü de while (isRunning) olarak değiştirmemiz iş parçacığını sonlandırmak istediğimizde isRunning
değerini false yapmamızın yeterli olmasını sağlayacaktır.

Abort metodunun çalışamayacağı durumlar da mevcuttur:

Durağan (static) sınıf constructor'ları kesinlikle abort metodu ile sonlandırılamazlar.


Catch/finally blokları ise ingilizcedeki tabiriyle onurlandırılmışlardır ve bu blokların içinde iken abort metodu çalışmayacaktır.
Bir diğer durum ise unmanaged bir kod çalıştırmışsanız karşınıza çıkacaktır. Bu durumda abort devreye giremeyecek ve bu kodun işi bittikten sonraki ilk managed
kodda abort metodu devreye girecektir.

Bu durumlarla karşılaştığınızda abort metodu hemen devreye giremeyecek ve çalışan kodların süresine göre devreye girmesi bir kaç saniye sürebilecektir. Bunu

http://www.gencklavyeler.com/forum/konu-c- le-mult thread-programlama-1-25955.html 7/9


20.11.2017 C# le Mult thread Programlama - 1
bilerek kodlarımızı yazmamız biz programcılar açısından faydalı olacaktır.

Thread.Interrupt()

Bu metod ile uyku modunda olan bir iş parçacığınızı zorla uyandırabilirsiniz. Bu aslında doğru bir çözüm olmasa da iş parçacıkları arası iletişimde kullanabileceğiniz
en basit yoldur. Bu metod ile bir iş parçacığınız uyandırmanız durumunda ThreadInterruptException hata mesajı üretecektir. Bu metodun Abort metodundan farkı
ise iş parçacığınızı sonlandırmamasıdır. Abort metodu az evvelde bahsettiğimiz üzere bir iş parçacığınız zorla durdurmak için kullanılabilirken Interrupt metodunu
sadece uyumakta olan iş parçacığını uyandırmak için kullanabilirsiniz. Interrupt metodunu kullanmadan önce iş parçacığınızın uyku modunda olup olmadığını
ThreadState özniteliğini kontrol ederek belirleyebilirsiniz. Buna rağmen unutmamanız gereken kontrolden hemen sonra eğer iş parçacığınız uyanırsa başınız dette
olabilir. Bu nedenle de kullanılması tehlikeli bir metoddur. Ancak aşağıdaki gibi bir durum söz konusu ise kullanamanız tehlike arzetmeyecektir.

1 static voidMain()
2 {
3 Thread isim = new Thread (isciMetod);
4 t.Start();
5 Thread.Sleep(5000);
6 // ön hazırlıkların yapıldığınız varsayalım.
7 t.Interrupt();
8 }
9
10 private void isciMetod()
11 {
12 try {
13 Thread.Sleep (Timeout.Infinite);
14 }
15 catch (ThreadInterruptedException) {
16 Console.Write ("Zorla Uyandırıldı");
17 }
18 // Burada işlerimizi yapabiliriz.
19 }

Unutmayın ki eğer bir iş parçacığı Interrupt metodu ile çalışmak üzere tasarlanmamışsa bu metodu kullanmak kesinlikle çok tehlikelidir. O nedenle sadece kendi
hazırladığınız ve Interrupt metodu ile çalışmak üzere çok dikkatlice tasarladığınız bir iş parçacığında kullanabilirsiniz. Yada en iyisi bu metodu hiç bir zaman
kullanmayın ve iş parçacıkları arası senkronizasyon konusunu okuduktan sonra bilgi sahibi olacağınız diğer yapıları kullanarak iş parçacıkları arası iletişimi ve
etkileşimi sağlayınız.

Aslında Interrupt ve Abort metodlarını hiç anlatmamayı düşünüyordum ama bu yazımda çok kanallı programlamanın temellerini anlattığım için ve burada yer alması
kaçınılmaz olduğundan anlatmak zorunda kaldım. Siz siz olun başka çareniz kalmadıkça her iki metoddan da uzak durmaya çalışın. Kendi yazdığım programların
bazılarında eş zamanlı çalışan iş parçacığı sayısı zaman zaman 50'nin üzerine çıkmasına rağmen bu metodları hiç kullanmadan tamanmışlardır. Ve programdan
çıkması en fazla 2-3 sn sürmektedir. Yani gerçekten iyi tasarlanmış bir programda bu metodlara ihtiyacınız olmayacaktır.

İş Parçacıkları ve UI Kontrolleri Arası Etkileşim

Bu yazımda son olarak bahsetmek istediğim konu ise ana iş parçacığının haricindeki diğer iş parçacıklarından UI nesneleri ile etkileşime geçemiyor olmanızdır.
Aslında bu söyleyiş tam doğru değil, doğrusu ise bir UI objesi ancak oluşturulduğu iş parçacığı ile etkileşebilmektedir. Normalde tüm nesneler ana iş parçacığı
tarafından oluşturulacağı için diğer iş parçacıkları UI nesneleri ile etkileşime giremeyecektir. Böyle bir işlem yapmaya kalktığınızda InvalidOperationException hatası
ile karşılaşırsınız. Bunun önüne geçebilmek için yapmanız gereken marshal kullanmaktır. Bunun içinde eğer yazmakta olduğunuz program bir Windows Form
programı ise Control.Invoke yada Control.BeginInvoke metodlarını kullanabilirsiniz, eğer WPF programı yazıyorsanız bu durumda dispatcher nesnesinin Invoke ve
BeginInvoke metodlarını kullanmanız gerekecektir. Aşağıda bir windows form programı ile hazırlanmış bir örnek bulabilirsiniz.

1 using System;
2 usingSystem.Windows.Forms;
3 using System.Threading;
4
5 namespaceThreadingLessons
6 {
7 public partial class Form1 : Form
8 {
9 public Form1()
10 {
11 InitializeComponent();
12 }
13
14 private void Form1_Load(object sender, EventArgs e)
15 {
16 new Thread( Worker ).Start();
17 }
18
19 private void Worker()
20 {
21 textBox1.Text = "Deneme";
22 }
23 }
24 }

Eğer yukarıdaki programı çalıştırırsanız karşınıza "The calling thread cannot access this object because a different thread owns it" hata mesajı çıkacaktır
(Kullandığınız Visual Studio ve Freamework versiyonuna göre bu mesaj değişebilir ama hatanın nedeni aynıdır). Bunun nedeni hata mesajından da anlaşılabileceği
üzere textBox1 kontrolüne oluşturulmuş olduğu ana iş parçacığı yerine yeni oluşturmuş olduğumuz iş parçacığından erişmeye çalışmamızdır. Bu illegal bir
operasyondur ve programın çalışmasını durduracak bir hata üretecektir. Bu problemi çözmek için ise programımızı aşağıdaki gibi değiştirmemiz yeterli olacaktır.

1 using System;
2 usingSystem.Windows.Forms;
3 using System.Threading;

http://www.gencklavyeler.com/forum/konu-c- le-mult thread-programlama-1-25955.html 8/9


20.11.2017 C# le Mult thread Programlama - 1
4
5 namespaceThreadingLessons
6 {
7 public partial class Form1 : Form
8 {
9 public Form1()
10 {
11 InitializeComponent();
12 }
13
14 private void Form1_Load(object sender, EventArgs e)
15 {
16 new Thread( Worker ).Start();
17 }
18
19 private void Worker()
20 {
21 Action action = () => textBox1.Text = "Deneme";
22 textBox1.Invoke( action );
23 }
24 }
25 }

Programımızı yukarıdaki gibi değiştirmemiz durumunda programın sorunsuz bir şekilde çalışacaktır.

Yalnız bu program c#'ın yeni versiyonlarında daha farklı da yazilabilirdi. Gerek lambda expression kullanarak (c# 3.0 da eklendi) gerekse c# 2.0 ile gelen
anonimous delege kullanımı ile bu kodu yeniden yazdığımızı düşünelim. Yine bu yeni versiyonumuzda Invoke metodu yerine de Framework tarafından bize
sağlanan ve basit senkronizasyon işlemlerimizde kullanabileceğimiz SynchronizationContext nesnesini de ilave olarak kullanalım.

1 using System;
2 usingSystem.Windows.Forms;
3 using System.Threading;
4
5 namespaceThreadingLessons
6 {
7 public partial class Form1 : Form
8 {
9 public Form1()
10 {
11 InitializeComponent();
12 }
13
14 private void Form1_Load(object sender, EventArgs e)
15 {
16 var synch = SynchronizationContext.Current;
17 new Thread( () => {
18 synch.Post( delegate {
19 textBox1.Text = "Deneme";
20 }, null );
21 } ).Start();
22 }
23 }
24 }

Bu programımızda öncelikle SyncronizationContext nesnesinin Current özelliği ile ana iş parçacığını işaretleyen synch isimli bir nesne oluşturuyoruz. Sonra ise yeni
bir iş parçacığı oluştururken parametre olarak bir metod göndermek yerine lambda expression kuıllanarak metodumuzu doğrudan yazıyoruz. Son olarak ise daha
önce oluşturmuş olduğumuz synch nesnesinin Post metoduna isimsiz bir delege oluşturarak parametre olarak gönderiyoruz ve programımızı tamamlıyoruz. Bu
programı da çalıştırmanız durumunda sorunsuz bir şekilde çalıştığını görebilirsiniz. Son bir not olarak ise gerek lambda expression ile oluşturduğumuz metodda
gerek ise isimsiz delegemizde rahatlıkla yerel değişkenlerimizi kullanabildiğimize dikkatinizi çekmek istiyorum.

Eklenti Dosyaları
Tırnak(lar)

« Önceki Konu | Sonraki Konu » Anahtar Kelimeleri Girin Konu İçinde Ara

Konuyu Paylaş:

Yazdırılabilir Bir Versiyona Bak


Foruma Git: -- C# Git
Bu Konuyu Bir Arkadaşına Gönder

Bu konuya abone ol

Bu konuyu görüntüleyen kullanıcı(lar): 1 Ziyaretçi

İletişim Genç Klavyeler En Üste Dön Arşiv RSS Beslemesi Yardım Türkçe {UTF-8} Git

Türkçe Çeviri: MyBB, Kodlayanlar MyBB, © 2002-2017 MyBB Group. Theme created by Justin S. Tarih: 20-11-2017, 14:12

http://www.gencklavyeler.com/forum/konu-c- le-mult thread-programlama-1-25955.html 9/9

You might also like