C# ile Paralel Programlama-1 (Task Sınıfı)

İlker Erhalim
4 min readJan 4, 2021
Kaboompics .com adlı kişinin Pexels’daki fotoğrafı

Merhabalar,

Paralel programlama genellikle sektöre yeni girmiş arkadaşlar için göz korkutan bir konu oluyor, ancak .net framework ile genel paralel programla problemlerinden soyutlanmış bir şekilde “business logic” e odaklanarak paralel uygulama geliştirmek artık kolay bir hal almış durumda. Ben de çok derinlere inmeden paralel programlanın temelleri, implementasyonları ve nasıl çalıştıkları ile ilgili bilgi vermeye çalışacağım.

Örnek kodların çıktısını anlaşılır bir şekilde göstermek için örneklerin tamamını Console uygulamasında geliştirdim ancak örnekler .net ortamının sağladığı diğer templateler üzerinde de çalışacaktır.

Kapsam oldukça geniş olduğu için konuları kendimce ayırıp “seri” formatında paylaşacağım.

Başlamadan önce paralel programlamanın amacının performans iyileştirmek olmadığını söylemek istiyorum. Paralel programlamanın amacı uygulamanın aynı anda birden fazla işi yapabilmesidir. Bunun sonucunda eğer performans artıyorsa bu ek bir faydadır.

.net ortamında paralel programlama denilince bir yazılımcının aklına 3 terim ve bu terimlerin karşılığı olan 3 sınıf gelir.

  • Thread
  • ThreadPool
  • Task

Thread

İşletim sistemi seviyesindeki gerçek threadi temsil eder, temel problemi maliyetidir, her threadin kendi kaynakları olduğu için belleği ve işlemciyi meşgul eder.

ThreadPool

Threadlerin bulunduğu koleksiyondur, yapması için iş göndermek ve alabileceği thread sayısını belirtmekten başla bir kontrol sağlamaz.

Task

Yapılması gereken işleri temsil eder, bir işi alt bir programda yapıp, tamamlanıp tamamlanmadığını ve eğer varsa dönen sonuçları ana programa söyleyebilir. İşletim sistemi seviyesinde bir thread oluşturmaz, yönetmesi ve kullanması kolaydır. Paralel programlama ile ilgili bir çok API task üzerine inşa edilmiştir. Bu yüzden yazıda Task sınıfı ve beraberinde kullanabileceğimiz özelliklerinden bahsedeceğim.

Kaynak: https://blog.slaks.net/2013-10-11/threads-vs-tasks/

Task Nasıl Kullanılır ?

.net ortamında System.Threading.Tasks (System.Runtime assembly) namespace üzerinden bir Task oluşturmanın 3 basit yolu vardır.

  • Task.Factory.StartNew methodu
  • Yeni bir Task instance oluşturmak
  • Task.Run methodu

Daha önce hiç Task kullanmadıysanız sonuçlar sizi şaşırtabilir, normal şartlarda beklediğimiz sonuç;

Terminal üzerinde sıra ile :

  • 1000 kere T1
  • DoWork Completed for T1
  • Main Thread Log 1
  • 1000 kere T2
  • DoWork Completed for T2

Yazmasını bekleriz, ancak örnek kodun çıktısına baktığımızda öyle olmadığını görüyoruz.

Gist üzerinde host edilmiş olan örnek kodun çıktısı. (T1 ve T2 yazıları sırasız bir şekilde terminale basılmış.)

Resimde de gördüğünüz gibi terminal çıktısındaki sıralama beklenenden oldukça farklı, bunun sebebi işlemlerin(Tasklerin) çalışmak için birbirini beklememesi.

Task İçerisinden Sonuçları Okumak

Task sınıfının generic bir child sınıfı mevcut, bu sınıf ile instance aldığınızda ya da StartNew/Run metodlarını generic overloadları üzerinden çalıştırdığınızda taskin verdiğiniz tipte bir dönüş yapmasını bekler. Geri dönen değere Result propertysi üzerinden erişilebilinir.

var task = new Task<int>(...);
var task = Task.Factory.StartNew<int>(...);
var task = Task.Run<int>(...);

Çalışma sırası burada da birinci örnekteki gibidir, hangi taskin önce çalışacağı koda bakılarak tahmin edilemez. Tahmin edilebilecek tek çıktı sonuçların ekrana yazdırıldığı sıralamadır.

Gist üzerinden paylaşılan örnek kodun ilk çalışması.(firstCollection önce çalışmış.)
Gist üzerinde paylaşılan örnek kodun ikinci çalıştırılması (secondCollection önce çalışmış)

Aynı kodun çalışmasına rağmen çalışma sıraları birbirinden farklıdır.

Taskin veya Tasklerin Tamamlanmasını Beklemek

Bir veya birden fazla taskin tamamlanmasını beklemek için çeşitli ihtiyaçlara göre birden fazla yol mevcut, bunların en çok kullanılan 3 tanesi;

  • Task örneği üzerinden Wait metodunu çalıştırmak: İlgili task tamamlanana kadar bir sonraki satıra geçilmez.
  • Task sınıfı üzerinden WaitAll metodunu çalıştırmak: Parametre olarak gönderilen tüm task örnekleri tamamlanmadan bir sonraki satıra geçilmez.
  • Task sınıfı üzerinden WaitAny metodunu çalıştırmak: Parametre olarak gönderilen task örneklerinden herhangi biri tamamlanmadan bir sonraki satıra geçilmez.

Yukarıdaki örnek çalıştırıldığında akış ve çıktı aşağıdaki gibi olacaktır.

Akş dygram(Tsk1 çalştır>Tsk1i bekle>Tsk2&Tsk3 çalıştır>Tsk1 & Tsk2 i bekle>Task3&Task4 çlştır>Task3 veya Task4 bekle
Örn kodun çıktısı ekrana yansıyan çıktı satırları sıra ile 16, 14, 19, 30, 23, 28, 33, 43, 47, 38

Bir Taskin Çalışmasını Durdurmak(İptal Etmek)

Bir task içerisinde yapılan iş gerektiğinde iptal edilebilinmelidir, bu yüzden Task içerisinde çalışacak bir geliştirme yapıyorken, işin iptal edilmek istenilip istenilmediğini sürekli kontrol edilmeli ve iptal isteğinden en kısa süre sonra iş iptal edilmelidir.

System.Threading namespace teki CancellationTokenSource sınıfı ile bir token oluşturulup, bu token üzerinden Task iptal etme işlemi yapılır.

Örnek çıktı: Belirli bir süre çalışan uygulama herhangi bir tuşa basıldıktan sonra terminale İşlem iptal edildi. yazar

Yukarıdaki örnek çalıştırıldığında Counter methodu ayrı bir task içerisinde çalışıp terminale sayıları yazmaya başlayacak, ancak bir tuşa basıldığında cancellationTokenSource.Cancel metodu çalıp taski iptal edecek.

--

--