Multithread Concurrency Parallel proqramlaşdırma
Multithread proqramlaşdırma və Concurrency proqramlaşdırma ən çətin başa düşülən mövzulardandır. Bu mövzuları anlamaq üçün əvvəlcə Threading mövzusuna baxaq. Bir prosesin birdən çox işi eyni anda etməsinə imkan verən strukturlara thread deyilir. Biz yazdığımız kodları multithread olaraq yazmasaq belə həmişə 1 thread işləyir bu thread Main Threaddir. Bundan başqa biz fərqində olmasaq belə CLR tərəfindən bəzi hallarda başqa threadlər yaradılır və bəzi işləri görmək bu threadlərə tapşırılır. Məsələn ‘Begin’ ilə başlayan və ya ‘…async’ ilə bitən metodları buna nümunə kimi göstərmək olar. Thread eyni anda sadəcə bir iş görə bilir yəni n dənə thread eyni anda n dənə işin görülməsi deməkdir. Daha sonra görəcəyik ki burada eyni anda dediyimiz anlayış da biraz fəqrlidir. Qısa olaraq indi deyə bilərik ki, multithread proqramlaşdırmada threadlər əslində bir birini gözləyir və bir core-da eyni anda sadəcə bir thread işləyə bilir amma threadlər arası keçişlər elə baş verir ki bu hiss olunmur. Əslində multithread proqramlaşdırma əməliyyat sistemi ilə əlaqəlidir və əməliyyat sistemləri var olandan bəri bu üsuldan istifadə olunur. Məsələn DOS sistemində eyni anda sadəcə bir proqram işlətmək olurdu. İndiki əməliyyat sistemlərində isə eyni anda 10-larla proqramı işlədər bilirk. Bunu multithread və concurrency –nin köməyilə edə bilirik. Bir CPU Core-da 3 threadin necə işləmə nümunəsi aşağıdakı şəkildə verilib. Bu şəkilə baxaraq daha yaxşı anlamağa çalışaq.
Kodlaşdırma hissəsinə keçmədən əvvəl bəzi anlayışları aydınlaşdıraq. Bunlar hələki sizə qarışıq gəlsə oxumadan keçə bilərsiz amma sonda yenidən bu anlayışlara qayıdıb anlamağa çalışın.
Bütün threadlər bizim tərəfimizdən idarə olunmalıdır. Yəni əgər thread yaratsaq sonra onu sonlandırmağı unutsaq arxada bu thread kompyuter söndürülənə qədər işləyəcək.Bunlara foreground threadlər də deyirlər. Default olaraq threadlər foregrounddur. Amma threadi background thread kimi işarələyib işlətsək main thread bağlananda bütün threadlər əməliyyat sistemi və ya CLR tərəfindən sonlandırılacaq. Hər bir thread içərisində yazılan kodlar eyni core-da işlənilir. Eyni işi bölərək fərqli core-larda işlətmək parallel programminglə mümkündür və daha sonra bunun haqqında geniş yazılacaq.
CPU–Komputerdə işləyən hər şey CPU üzərində işləyir. Klavyaturada yazılan hər hərf mouse-un hər hərəkəti belə CPU üzərindən keçir. CPU -da 2 əsas hissəsi car Control unit və arithmetic/Logic unit
Control Unit –elektrik siqnalları ilə birbaşa kompyuter sisteminə işləniləcək şeyləri göndərir.
Aritmetich Logical unit (ALU) — Logical və riyazi bütün əməliyyatları özüəndə saxlayır və həll edir.
User Thread– Bizim danışdığımız thread user threaddir. Bir şey də qeyd etmək olar ki CPU sadəcə kernel thread işlədə bilir və biz yazdığımız bütün threadlər əslində kernel threadlə qarşılaşdırılaraq işlədilir.
Kernel Thread — Kernel thread proses içində yerləşən və kernel səviyyəsində idarə olunan thread növüdür. Normalda bizim accesimiz yoxdur ancaq kernel extension və ya driver yazırıqsa kernel thread istifadə edirik.
Threadpool –Threadlərin yerləşdiyi yerdir thread kolleksiyasıdır deyə bilərik. Gördüyü iş ala biləcəyi thread sayını təyin etmək və ediləcək işləri göndərməkdən ibarətdir.
Process–İşləyən proqramlara proses deyirik. Bir proqram məsələn word excel əgər run olmayıbsa bir proqramdır run olduqdan sonar isə prosesdir. Bir proqramda birdən çox proses ola bilər.
Child Process– Bir proses içərisində başqa proses başlada bilər və buna child proses deyilir.
CPU(CPU bound) process — Bu əməliyyatlarda CPU üzərində aparılır və demək olar ki diskdən istifadə etmir. Hesablama əməliyyatlarını buna misal göstərmək olar.
IO (IO Bound) process –IO əməliyyatları CPU-da çox iş görmür əsasən işləri İO tərəfdə yəni disk vəya Ram üzərində görür. Fayla yazmaq fayldan oxumaq buna misal ola bilər və ya http ilə bir linki və ya apini çağırırıqsa. Bu proseslər daha çox asinxron şəkildə yazılır.
Task — etmək istədiyimiz şeydir yəni hədəfdir. Bir taskı yerinə yetirmək üçün çoxlu sayda işçi(thread) çalışa bilər. Daha sonra asinxron proqramlaşdırma hissəsində tez-tez taskdan istifadə edəcəyik.
Multithreading — MultiThreading prosessorun (CPU) (və ya çoxnüvəli prosessorda bir nüvənin) “eyni vaxt”da birdən çox işi icra edə bilmək qabiliyyətidir.
Parallelism –Birdən çox CPU və ya Core-da eyi anda(həqiqi mənada eyni anda) əməliyyat aparılması deməkdir.
Parallel programming — Bir problemi həll etmək bir prosesi çalışdırmaq üçün birdən çox resource-un eyni anda istifadə edilə bilməsidir. Biz resource deyəndə əsəsn CPU nəzərdə tuturuq.
Concurrency –Birdən çox threadlə işləmək üçündür. Concurrentlik əldə etmək üçün Mutithread programming -dən istifadə edilir. Yəni Concurrency hədəfdir multithreading isə belə deyə bilərik ki bu hədəf çatmaq üçün istifadə olunan tool-dur.
Multithread programming — Bir əməliyyatın edilməsini birdən çox işçiyə həvalə etməkdir.
Multitasking –Eyni vaxtda birdən çox prosesin etni vaxtda işləyə bilməsidir. Multithread bir proses içində eyni vaxtda birdən çox threadin işləməsidir. Multitasking eyni vaxtda əməliyyat sistemində birdən çox prosesin işləyə bilməsidir. Bəzən Multiprogramming olaraq da görə bilərsiniz. Tasklar 1 prosessor və 1 Core üzərində işləyir.
Multiprocessing — Eyni vaxtda birdən çox prosessor və ya Core-daparalel şəkildə işləyə bilməsidir.
Sinxron–Əməliyyatların ardıcıl şəkildə yerinə yetirilməsidir. Hər bir əməliyyat özündən əvvəlki əməliyyatın bitməsini gözləyir. Kodlarımız yuxarıdan aşağı ardıcıl şəkildə işləyir.
Asinxron– Asinxron proqramlaşdırmada əsas məsələ threadlər deyil. Əsas məsələ odur ki , bir çox əməliyyat yerinə yetirilir və bu bir birini bloklamadan baş verir. Məsələn sadə dildə desək bir metodu çağırırıq amma nəticəni gözləmirik proqramımız başqa işlərlə məşğul olur və nəticə hazır olanda artıq biz bunu da istifadə edə bilərik.
Hyper-Threading –tək bir core-un 2 fərqli core kimi işləyə bilməsini təmin edir.
CPU Schedulling–İşləməy hazır proseslər arasından lazımi prosesi seçmək və seçdiyi prosesi işlətməyə başlama əməliyyatıdır.
Critical Section–Birdən çox thread və ya proses tərəfindən paylaşılan kod hissələrinə critical section adı verilir. Bu halda kodu normal kontrol edə bilmək üçün mutex, semaphore, monitor, lock kimi metodlardan istifadə edəcəyik.
Race Condition–Birdən çox thread və ya prosesin eyni anda eyni resursa(dəyişənə) müraciət edərək onu dəyişdirməyə çalışmasıdır.
Context Switch–Dediyimiz kimi eyni anda bir core-da bir thread və ya proses işləyir. Yəni proseslər arası keçişlər baş verir. Bir prosesin dayanıb digər proses işə başladıqda əvvəlki prosesin bütün məlumatları save olunur və sonar yenidən bu prosesin növbəsi gəldikdə qaldığı yerdən davam edir. Buna context switching deyilir.
Process Control Block (PCB) — context switching -də dediyimiz yadda saxlama prosesi PCB data structure-nu istifadə edir. Bu əməliyyat aşağıdakı kimidir.
Lock — Bir thread içərisində görülən işin digər threadlər tərəfindən dəyişdirilə bilməməsi üçün istifadə olunur.
Deadlock — 2 və daha çox prosesin qarşılıqlı olaraq bir birilərini kilitlədiyi resurslara muraciət etmək istəyən vaxt baş verir.
Tək core-lu bir kompyuterdə içində heç bir kod yazılmayan sonsuz döngü çalışdırsaq CPU-nun getdikcə çox şişdiyini görəcəyik. Bunun qarşısını almaq üçün müxtəlif metodlar var. Thread.Sleep(1000) ən profesional olmayan metoddur. Ümumiyyətlə Sonradan idarə etmək çətin olduğu üçün birbaşa threadlərlə işləmək məsləhət görülmür. .NET -də hazır frameworklər var bunun üçün və çox vaxt onlardan istifadə olunur. Amma bu frameworklər hamısı əlbəttə ki arxada thread işlədir. Ona görə də hər bir proqramçı threadin necə işlədiyini bilməlidir.
Indi c#-da thread yaradaq. System.Threading namespace-dən istifadə edəcəyik.
Result:
Main Thread işinibitirsədə worker thread işləməyə davam edir. Main thread bittikdə worker thread də dayansın istəsək background thread olaraq işarələməliyik. thread1.IsBackground = true;
Result belə olacaq:
Metoda parameter göndərmək üçün thread Start metoduya parameter göndərə bilərik.
Bu şəkildə sadəcə bir parametr göndərə bilirik və tipi obyekt olmalıdır.
Lambda expression istifadə edərək çoxlu parametr göndərə bilərik.
Thread thread1 = new Thread(()=>MyWorker(3,8));
Thread1.Start();
Thread.Sleep() : Bu metod vasitəsilə Thread- i gözləmə vəziyyətinə ala bilərik. Aldığı parametr millisaniyədir. 5000 yazaraq 5 saniyə gözləmə edə nilərik. Thread.Sleep(5000);
Join: Bəzi hallarda bir thread başqa threadin çalışıb bitməsini gözləməlidir və digər thread bitdikdən sonra çalışmalıdır. Join metodu bunun üçün istifadə olunur.
Main thread işləyibbitmir worker thread-i gözləyir. Main thread başlayır sonra worker thread başlayır işləməyə və bittikdən sonra main thread çalışır.
Başqa bir numunə:
Abort() : C#-ın əvvəlki versiyalarında threadı dayandırmağın başqa bir yolu da abort metodundan istifadə etmək idi. .Net 5 sonrası bu metod dəstəklənmir.Hər hansı thread üçün bu metodu çağırdıqda əvvəlcə statusu AbortRequested olaraq dəyişir daha sonra isə Aborted olur. Bu metodu işlətdikdə çox vaxt ThreadAbortException xətası alırıq ona görə də həmişə try catch içində yazmaq yaxşıdır.
İnterrupt(): Bu metodu sleep modda olan threadi oyandırmaq üçün istifadə edə bilərik.
ThreadState: threadin statusunu döndürür.
Multithread proqramlaşdırmada iki əsas məsələ var.
1. Dəyişənlərin sinxronizasiyası
2. Thread-lərin sinxronizasiyası
Dəyişənlərin sinxronizasiyası
Dəyişənlərin sinxronizasiyası deyəndə bir dəyişənin dəyərinin bir thread-də dəyişdikdə digər thread-lərin də bundan xəbərdar olmasıdır. Bu CLR tərəfindən edilmir və bunu etmək biz proqramçıların üzərinə düşür.
1. Bloklama metodları — Sleep, Join, EndInvoke
2. Kilitləmə — lock (Monitor.Enter/Monitor.Exit), Mutex, SplinLock, Semaphore, SemaphoreSlim, reader/writer kilitlər
3. Siqnal strukturları — Event wait handler, Monitor.wait/Monitor.Pulse, CountDownEvent, Barrier
4. Bloklanmayan strukturlar — Thread.MemoryBarrier, Thread.VolatileRead, Thread.VolatileWrite, volatile variables, InterLocked
Kilitləmə mexanizmləri arasında ən sürətlisi Lock -dur. Lock kodun bir hissəsini kilitləmək və digər thread-lərdən qorumaq üçün istifadə olunur. Kilitləmək üçün referans tipdə bir dəyişən istifadə olunur. Eyni vaxtda başqa thread də bu referans tipində olan dəyişəni kilitləyə bilmədiyi üçün Lockun altında kodlarımızı rahat şəkildə yaza bilərik.
lock ( Object) {
//….
}
Lock sürətli çalışır amma istifadə edərkən diqqətli olmaq lazımdır. Lock altında elə də çox kod yazmayın. Lock-lanacaq obyekti seçərkən də diqqətli olmaq lazımdır. Əgər lock-ladığınızobyekti lock blokunda dəyişdirməyə çalışsanız deadlock yarana bilər. Lock(this) şəklində istifadədə həmişə deadlock yaradacaq. Bundanbaşqa bir obyekt yaradıb bütün lock mexanizmlərində onu istifadə etmək də mənasız gözləmələrə səbəb olacaq.Çox vaxt Lock istifadə edəndə başqa bir readonly local referans tipində dəyişən yaradılaraq istifadə edilir.
readonly object lockObject=new();
Gəlin bir class yazaq.
Class Car{
int suret=0;
public void IncSpeed(){
suret++;
}
public void DecSpeed(){
suret — ;
}
}
Bu class thread-safe class deyil. Bu classda artırma və azalma metodlarını müxtəlif threadlərdə çağıranda bizim gözlədiyimiz nəticələri əldə etməyə bilərik. Bu classı thread-safe yazaq.
Class Car{
readonly object lockObject=new();
int suret=0;
public void IncSpeed(){
lock(lockObject)
suret++;
}
public void DecSpeed(){
lock(lockObject)
suret — ;
}
}
Monitor.Enter / Monitor.Exit
Bu metodlar Lock çalıştırlanda arxa planda avtomatik olaraq işləyir. Yəni əslində bu Lock bu metodları çalışdırmağın qısayoludur. Yuxarıdakı class-da incspeed metodunu bu şəkildə yazaq.
Car c = newCar();
c.IncSpeed(5);
c.writeInfo();
classCar
{
readonlyobjectlockObject = new();
intsuret = 0;
publicvoidIncSpeed(int value)
{
//lock (lockObject)
Monitor.Enter(lockObject);
try
{
suret+=value;
}
finally
{
Monitor.Exit(lockObject);
}
}
publicvoidDecSpeed()
{
lock (lockObject)
suret — ;
}
publicvoidwriteInfo()
{
Console.WriteLine(suret);
}
}
Çox nadir hallarda Monitor.Enter ilə lock baş verməyə bilər bunun üçün lockTaken istifadə olunur.
Car c = newCar();
c.IncSpeed(5);
c.writeInfo();
classCar
{
readonlyobjectlockObject = new();
intsuret = 0;
publicvoidIncSpeed(int value)
{
//lock (lockObject)
boollockTakenObject = false;
Monitor.Enter(lockObject, reflockTakenObject);
try
{
suret += value;
}
finally
{
if (lockTakenObject)
Monitor.Exit(lockObject);
}
}
publicvoidDecSpeed()
{
lock (lockObject)
suret — ;
}
publicvoidwriteInfo()
{
Console.WriteLine(suret);
}
}
Monitor.TryEnter metodunu da çağıra bilərsiz. Millisecond dəyəri verib əgər lock yaranmazsa false döndürə bilərsiz. Birdə bir şey yadda saxlamaq lazımdır ki iç içə lock istifadə etdikdə həmişə deadlock yaranacaq.
Mutex
Mutex də Lock-a bənzəyir. Lockdan fərqli olaraq həmdə proseslər arası locking edə bilməsidir. Bir də bir şey bilmək vacibdir ki lock -dan çox yavaş işləyir vacib olmadıqca istifadə etmək düzgün deyil.
Semaphore
Semaphore-u belə başa düşə bilərik ki, çalışa biləcək thread sayını müəyyən edir sonra bir thread işləməyə başlayanda semaphore baxır boş yer varsa thread-i işlədir yoxdursa gözləyir. Semaphore-un digərlərindən fərqi fərqli threadlər birbirlərinin semaphore-larını release metodu ilə aça bilirlər.
Thread[] threads = new Thread[10];
Semaphore sem = newSemaphore(3, 3);
for (inti = 0; i< 10; i++)
{
threads[i] = newThread(RunThread);
threads[i].Name = “Thread” + i;
threads[i].Start();
}
voidRunThread()
{
Console.WriteLine(“{0} is waiting…”, Thread.CurrentThread.Name);
sem.WaitOne();
Console.WriteLine(“{0} start!”, Thread.CurrentThread.Name);
Thread.Sleep(500);
Console.WriteLine(“{0} exit!”, Thread.CurrentThread.Name);
sem.Release();
}
Non-Blocking sinxronizasiya
Blocking sinxronizaiyada yazdığımız kod parçasına eyni vaxtda sadəcə 1 thread istifadə edə bilirdi. Eyni vaxtda birdən çox thread tərəfindən əlçatan olan kodlar yazmaq üçün non-blocking sinxronizasiya üsullarından istifadə olunur. Əslində belə problem var ki compiler, CLR və ya prosessor yazdığımız kodların sırasını dəyişə bilər. Bu çox vaxt daha effektivliyi artırmaq üçündür. Amma bəzən bu yazdığımız kodların işləməsində problemlər yaradır. Bu halların baş verməməsi üçün MemoryBarrier strukturlardan istifadə edə bilərik.
Volatile
volatile kodlarımızn yer dəyişməsinin qarşısını alır və əlbəttəki non-blocking olduğu üçün volatile ilə təyin edilən dəyişənlər bütün thread-lər tərfindən oxuna və dəyişdirilə bilir. Volatile dəyişənlər OS tərəfindən edilən optimizasiya proseslərindən kənar qalır. Bu keyword -lə yazılan dəyişənləri oxumaq və ya dəyişmək istədikdə acquire-fence sürətli blocking mexanizmi istifadə olunur. Bunu səbəbi odur ki digər oxuma yazma prosesləri ilə qarışmasın. Daha sonra release-fence istifadə edilərək blocking sürətli şəkildə qaldırılır. Buna half-fence deyilir.
MemoryBarrier
Memory Barrier metodları da kodun sırasının dəyişdirilməsinin qarşısını almaq üçün işlədilir və bunu full-fence olaraq edir. Kodun əvvəlində və ya sonrasında bu metodu çağırmaq kifayət edir. MemoryBarrier-ləri sadəcə atomic əməliyyatlar üçün istifadə edə bilərik. Atomik o deməkdir ki arxa tərəfdə sadəcə bir addımda əməliyyat bitir. Məsələn
int x;
long y;
x=5 //atomikdir
y=55;//64 bitdə atomikdir, 32 bitdəyox
y=x; //atomic deyil read+write
full-fence bundan başqa lock, monitor.enter/monitor.exit, InterLocked bütün metodlarda, thread signal strukturlarda və s baş verir. MemoryBarrier Lockdan iki dəfə sürətlidir.
InterLocked sinifi
Multithread proqramlaşdırmada dəyişənlərlə bağlı problemləri həll edəcəyiniz üsullardan biri də budur. Sürətlidir və içərisində çoxlu faydalı metodlar var və ən əsası atomic olmayan əməliyyatlarda da istifadə edəbilərsiz.
SpinLock
Buraya qədər danışdıqlarımız hamısı blocking mexanizmi ilə bağlı idi. SpinLock isə spinning edir. Yuxarıda spiningin nə olduğu haqqında məlumat vermişdik. SpinLock əməliyyat sistemi ilə əlaqəyə keçmədən nə baş verirsə CPU üzərində baş verir. Bura qədər işlədyimiz üsullar klas olaraq yaradılmışdı. SpinLock isə strukturdur. Spinlock həmdə garbagecollectordan da asılı deyil. Bir şey də qeyd etmək lazımdır ki spinlock-ları parametr kimi göndərəndə kopyasını yaradıb göndərir və başqa threadlər tərəfindən də dəyişikliklər və lock-lar görünməyə bilər. Ona görə də həmişə ref parametri ilə parametr kimi işlətmək lazımdır. SpinLock həmişə try-catch içərisində yazılmalıdır. Spinlock-un kod hissəs Monitor.Enter Monitor.Exit kodlarına oxşayır. Həmişə lockTaken işlətmək lazımdır. Bir şeyi də yadda saxlamaq lazımdır ki SpinLock yavaşdır və görüləcək iş çox uzun çəkmirsə SpinLock istifadə edə bilərsiz.
var spinLock = new SpinLock (true);
bool lockTaken = false;
try
{
spinLock.Enter (ref lockTaken);
}
finally
{
if (lockTaken) spinLock.Exit();
}
SpinWait
SpinWait locklama etmədən dəyişənlər üzərində dəyişiklik edə bilir. Həm də bloklama da etmir. Bunun istifadəsi və istifadə edilməsinə düzgün qərar vermək çox çətindir. Amma düzgün yerdə istifadə edilərsə çox effektiv olacaq.
Threadlərdən sonra biraz da tasklar barədə danışaq. .Net-də task yaratmağın 3 yolu var.
1. Task classından instance yaratmaqla
2. Task.Factory.StartNew metodu
3. Task.Run metodu
Task instance-ı ılə task yaratdıqdan sonra Start metodu ilə çalışdrımaq laızmdır.
Task.Factory.StartNew ilə task yaradaq.
Task.Run ilə nümunəyə baxaq.
Bəzi hallarda bir tasklarla işləyəndə bir taskın işləməsi üçün başka taskın işini bitirməsini yəni taskların bir-birini gözləmək istəyə bilərik. Bu hallarda wait metodlarından istifadə edə bilərik. Bunun üçün 3 metod var.
1. Wait metodu — Bir taskın işləyib bitməsini gözləmək üçün istifadə edilir.
2. WaitAll metodu — Parametr olaraq göndərilən bütün taskların işləyib bitməsini gözləmək üçün istifadə edilir.
3. WaitAny metodu — Parametr olaraq göndərilən taskların hər hansı birinin bitməsini gözləmək üçün bu metoddan istifadə olunur.
Bəzi hallarda bir işin bitməsindən sonra başqa bir işin davam edilməsini istəyə bilərik. Bu halda continue metodları istifadə olunur.
ContinueWith — task bitdikdə davam et
ContinueWhenAny -> Hər hansı bir task bitdikdə
ContinueWhenAll -> Bütün tasklar birdikdən sonra davam et
Istifadəsinə baxaq:
Bundan başqa TaskContinuationOptions Enumının istifadəsini araşdıra bilərsiz. Bundan başqa CancellationTokenSource istifadəsi vacib mövzulardan biridir. Hər hansı bir task başlayıb və uzun müddət işləyir və biz bunu dayandırmaq istəyirik və ya bir xətayla qarşıladşıqda bu taskı dayandırmaq istəyirik. Bu hallarda CancellationTokenSource istifadə edə bilərik. CancellationTokenSource-nin necə işlədiyinə aid bir koda baxaq. Metodlara parametr olaraq CancellationTokenSource göndərilir və Rəqəmlər yazılır müəyyən vaxt aralığında stop metodunda cancel olunur.
Asinxron metodlar geriyə bir dəyər dönmürsəyəni normal halda void metoddursa geri task döndürməliyik. Əgər geriyə hər hansı dəyər döndərmək istəyiriksə Task<T> yazmalıyıq.
Taskları necə istifadə edə bilərik öyrəndik. Ancaq bəzi metodalrda geri dönən dəyər Task yox ValueTask olduğunu görürük. Hansı hallarda ValueTask istifadə etməliyik ona baxaq. Aydındır ki asinxron işləməsini istədiyimiz metodlarda Task istifadə edirik. Məntiqli olmasada məsələn deyək ki, belə bir metod olsun.
public async Task<Decimal> GetPriceAsync(bool isPriceChanged,decimal price)
{
if(isPriceChanged)
{
return await CalculatePriceAsync();
}
return price;
}
Bu metod bəzi hallarda asinxron çalışır. Price dəyişmədiyi hallarda isə parametr kimi gələn price dəyişənini geri qaytarır yəni asinxron bir iş görmür. Ancaq buna baxmayaraq hər çağrıldıqda yeni bir Task tipi yaradılır. Task bir class yəni referans tip olduğu üçün heap-də hər dəfə yaranan bu tasklar garbage collectora da əlavə yük gətirir. Ancaq ValueTask istifadə etsək əlavə yüklənmə olmur. Ona görə ki ValueTask structdur və value tipdir. Bu həmdə o deməkdir ki daha sürətlidir. İstifadəsi isə çox sadədir. Sadəcə olaraq yuxarıdakı metodda Task-ı ValueTask olaraq dəyişdirmək lazımdır.
public async ValueTask<Decimal> GetPriceAsync(bool isPriceChanged,decimal price)
{
if(isPriceChanged)
{
return await CalculatePriceAsync();
}
return price;
}
ValueTask Taskdan fərqli olaraq bir çox məhdudiyyətləri var. Əgər resultda WhenAll, WhenAny istifadə ediləcəksə onda ValueTask istifadə etmək düzgün deyil. Əgər bu çəkildə istifadə etsək yenidən AsTask() metodunu istifadə edib Taska çevirmək lazım olacaq. Yəni əgər metodu saədəcə await edib çağıracayıqsa ValueTask istifadə edə bilərik digər hallarda istifadəsi çox da məsləhətli deyil.
Yuxarılarda bir yerlərdə qeyd etdiyim kimi IO based əməliyyatlarda async-await istifadə etmək daha yaxşıdır. IO based proyektlərdə asyn await istifadəsi çox böyük fayda gətirəcək CPU based proyektlərdə isə bu o qədər də əhəmiyyətli olmayacaq. Web proyektdə və ya apilərlə işləyirsinizsə async await çox istifadə edəcəksiniz. async-await də əsas məqsəd odur ki requestlər bloklanmasın , applicationun işləməsində hər hansı gözləmələr olmasın. Yəni async await istifadəsini birbaşa threadlərlə əlaqələndirmək o qədər də doğru deyil. Əslində async await istifadə edərkən arxa tərəfdə state machine yaranır və bu state machine içərisində callback-lərlə idarə edir. Belə sadə bir metoda və onun decompile olunmuş halına baxaq.
Parallel programming mövzusu əlavə olunacaq (Olunmadı).