System.Threading.Tasks

Task sınıfı, bir değer döndürmeyen ve genellikle eş zamansız olarak yürütülen tek bir işlemi temsil eder.

Cihat Solak
lTunes Tribe
6 min readNov 5, 2022

--

Asenkron & Multithread Programlama içeriğimizden sonra asenkron işlemleri temsil eden Task sınıfının metotlarını inceleyeceğiz.

Task.ContinueWith

Task içerisindeki işlem tamamlandıktan sonra çalışacak kodları (callback mekanizması gibi) burada yer alır. Daha önceleri bir asenkron metot kodlamasında, asenkron metot bittiği zaman çalışacak bir metot tanımlanırdı. Haliyle asenkron metot çalıştıktan sonra tanımlanan callback metot otomatik olarak devreye girerdi. Bu eski senaryonun yerine Task.ContinueWith dediğimizde ilgili task işlemi bittikten sonra ContinueWith içerisinde bulunan kodlar işletilir.

ContinueWith, parametre olarak action delegate’si almaktadır. Net Framework içerisinde action delegate’leri ön tanımlı olup, genellikle parametre alan fakat geriye hiç birşey dönmeyen metotları işaret etmektedir. Günümüzde ise ContinueWith metotu yerine async/await ikilisini kullanarak kodun okunabilirliğini arttırabiliriz. Ama illa Async/Await kulanmak istemiyorsak bir metot oluşturup bu metotu ContinueWith’e parametre olarak tanımlamak daha efektif olabilir.

Task.WhenAll

Bu metot parametre olarak Task Array’i alır (params Task[] tasks) ve parametre olarak aldığı task’lar tamamlanana kadar bekler. Kritik nokta Task’ların bir tanesinin tamamlanması değil, tümünün tamamlanması gerekmektedir. Örneğin 3–5 adet asenkron metot başlattınız (await ile taahhüt etmediğinizi varsayıyorum) ve kodun bir belli bir yerinde başlattığınız tüm asenkron çağrıların bittiğinden emin olup, sonuçlarını elde etmek istiyorsunuz. Bu durum da Task.WhenAll metotu hayat kurtaracaktır.

WhenAll — Result

Yukarıdaki örneğin incelediğimizde GetContentAsync metotunun farklı thread’ler üzerinde çalıştığını console çıktısından görebiliyoruz. Daha önceki içeriklerde de belirttiğim üzere asenkron metot 1 thread üzerinde çalışabilir de çalışmayabilir de. Bizim şu anda önemsediğimiz konu main thread’in bloklanmamasıdır.

Task.WhenAny

Bu metot parametre olacak Task Array’i alır (params Task[] tasks) ve parametre olarak aldığı task’larden ilk biten task’ı bize döner. Yani parametre olaran 10 task verdiysek bunlar arasında işi ilk biten task’i geri döner.

Task.WaitAll

Task.WhenAll gibi parametre olarak TaskArray almakta olup ilgili task’lar tamamlanana kadar beklemektedir. Kritik Nokta -> Task.WhenAll metotu main thread’i bloklamıyorken WaitAll metotu main thread’i bloklamaktadır. Bir windows form gibi ara yüze sahip uygulama hayal edersek, Task.WaitAll içerisindeki işlemler tamamlanana kadar pencere donar, farklı işlem yapamayız. Bu nedenle kullanımı tavsiye edilen yöntem değildir. Senkron kodlamaya ihtiyacınız olduğu yerde kullanabilirsiniz.

Aralarındaki farkdan bahsetmek gerekirse, WaitAll metotu parametre olarak milisaniye cinsinden değer alıyor. Örneğin parametre olarak 5000 milisaniye geçtiğimizde WaitAll metotu geriye true/false değeri dönmektedir. Anlaşılacağı üzere vermiş olduğumuz süre zarfında task işlemleri gerçekleşirse true, aksi halde false değeri geri döner. Eğer milisaniye cinsinden parametre geçmezse geri dönüş tipi void olacaktır.

Task.WaitAny

WaitAll metotu gibi main thread’i bloklamaktadır. Örneğin UI Thread üzerinde çalışıyorsanız UI Thread’i bloklayacağından dolayı ekran donacaktır. Parametre olarak Task[] array’i alır. Parametre olarak aldığı task’lardan herhangi biri tamamlandığı zaman geriye tamamlanan task’ın index numarasını döner. Bu index numarası üzerinden tamamlanan task’ı alabiliriz. Milisaniye alan overload metotu da bulunmaktadır.

Çağrıldığı yerde main/primary/ui thread’i bloklar.

Task.Delay

Asenkron gecikme sağlar, bu gecikmeyi gerçekleştirirken main thread’i bloklamaz. Thread.Sleep() main/ui/primary thread’i bloklarken task delay metotu bloklama gerçekleştirmez.

Thread.Sleep(), main/ui/primary thread’i milisaniye cinsinden bloklama gerçekleştirerek gecikmeyi sağlar. Senkron mantığında çalışır.

Task.Delay() ise main thread’i bloklamadan gecikme sağlar. Asenkron mantığında çalışır.

Örneğin yukarıdaki senaryoda GetContentAsync metotunu 3 kere çağırdığımızda toplamda 3 x 5 = 15 saniye BEKLEMEYİZ. Asenkron kodlama yaptığımız için ilk thread metota girdiğinde sadece ilgili thread 5 saniye bloklanacaktır. 3 istekde ortalama aynı anda metotu tetikleyeceğini düşünürsek bu işlemin tümü 5 saniye kadar sürecektir. Senkron olsaydı ilk çağrı 5 saniye, ikincisi 5 saniye, üçüncüsü 5 saniye saniye beklerdi ve o zaman toplamda 15 saniye beklemiş olurduk.

Task.Run

Asenkron metotlar farklı thread üzerinde çalışmak zorunda değildir. Duruma göre değişkenlik gösterir. Hatırlayalım, asenkron kodlamamızdaki en temel amaçlarımızdan biri çağrı anında main/primary/ui thread’i bloklamamaktı.

Peki, biz yapacağımız bir işin farklı thread üzerinde çalışmasını istiyorsak ne yapacağız? Task.Run metotunu kullanacağız. Task.Run metotu işleteceği kodları tamamen ayrı bir thread üzerinde çalıştırır. Bizzat yapılacak işlemi ayrı bir thread üzerinde çalıştır kardeşim diye direktif vermiş oluruz. Biraz tehlikeli görünüyor, dikkat etmekte fayda var.

Best practices açısından işlemciyi hırpalayacak metotları ayrı bir thread üzerinde çalıştırmak uygun olandır. Örneğin yapacağınız işlem içerisinde ciddi algoritma/matematik gerektiren işlemler yoğun şekilde mevcutsa, bu gibi işlemleri tamamen ayrı bir thread üzerinde çalıştırmak uygun görülebilir. Bunun dışında dosya okuma/yazma, veri tabanına okuma/yazma veya bir web sitesine istek yaparken ayrı bir thread kullanılması uygun değildir. Çünkü bu gibi yapacağınız işlemlerde belki de farklı bir thread kullanımına o anda ihtiyaç olmayacak. Böyle bir senaryoda Task.Run kullanırsanız ihtiyaç olmadığı halde thread kullanımına sebebiyet vermiş olacaksınız. Bu da best practices açısından kötü bir yöntemdir. Çözüm olarak performans ve kaynakları verimli kullanarak thread’leri bloklamamak adına asenkron metot kullanımı uygundur.

Task.Factory.StartNew()

Genel itibariyle Task.Run metoduyla aynı işleve sahiptir. Bu metotda içerisine yazmış olduğumuz kodları farklı bir thread üzerinde çalıştırdığı için dikkat edilmesi gerekmektedir. Task.Rın metotundan farkı ise şudur: Run metotuna task oluştururken bir nesne (obje) veremiyorken StartNew() metotuna bir nesne (obje) verebiliyoruz. Task işlemi tamamlandıktan sonra vermiş olduğumuz nesneyi kullanabiliyoruz. Buradaki obje referans tip ya da değer tip olabilir.

Task.FromResult

Parametre olarak generic tip alıp geriye verilen tipe göre Task<TResult> dönmektedir. Örneğin bir metotdan daha önce almış olduğunuz static veriyi dönmek istiyorsak, kullanım için bir seçenek olabilir.

CancelationToken

Bir asenkron metot başlattığımızda birçok sebepten ötürü başlattığımız işlemi iptal etmek isteyebiliriz. Örneğin dosya okuma işlemi yapıyoruz ve 10–15 dakika sürecek bir iş. Bu işlemi 3.üncü dakika veya herhangi bir T anında yapmaktan vazgeçtiğimizde iptal etmek isteriz. Bu gibi durumda cancelationtoken kullanırız. Nasıl? Asenkron operasyon başlatırken bir token veriyoruz ve bu token ile başlatmış olduğumuz asenkron operasyonu iptal etmiş oluyoruz.

Asenkron metotun cancelationtoken alabilmesi için mutlaka kurucusunda (constructor) overload metotu olması gereklidir. Bundan ötürü her asenkron metot cancelationtoken alabilir diye koşul koyamayız.

Örneğin Web MVC uygulamamızda kullanıcı excel export yapmak istedi ve gerekli action’a isteğini gerçekleştirdi. 1–2 dakika sonra ihtiyacının kalmadığını düşündüğünde isteği iptal etmek isteyebilir. Uygun senaryo şu şekilde.

Task.Result

Senkron bir metot içerisinde asenkron bir çağrım yaptığımızda Result property’si ile sonucu elde edebiliriz fakat Result dediğimiz anda o anki thread hangisi ise o thread bloklanır. Şöyle düşünelim. Result’ı kullanmak ara yüze sahip bir uygulamada işlem süresine bağlı olarak ara yüzü kilitleyecektir. Kullanıcı result property’sinden dönen değeri elde edene kadar geçen sürede ekranda farklı hiçbir şey yapamayacaktır. Çünkü thread result property’sini elde etmekle meşguldür.

Task Instance Properties

Task sınıfının IsCanceled, IsCompleted, IsCompletedSuccesfully, IsFaulted gibi önem arz eden property’leri bulunmaktadır.

  • IsCanceled: İşlemde olan task’ın iptal edildi mi?
  • IsCompleted: İşlemin tamamlandı mı? (başarı göz ardı edilir.)
  • IsCompletedSuccesfully: İşlem başarıyla tamamlandı mı?
  • IsFaulted: İşlem hata aldı mı?

Wir Sehen Uns 😎

--

--