Bu konu esasında karşıma çıkan bazı problemler sayesinde daha detaylı şekilde araştırma fırsatı bulduğum bir konu oldu.
Örneğin bir web sayfasında bulunan dosya görüntüleme butonuna tıkladığımızı düşünelim. Response dönene kadar geçen sürede tüm uygulama bu cevabın gelmesini bekleyebilir. Senkron programlamada eğer bir işlem için bekleniyorsa, bu durum uygulamadaki diğer tüm işlemleri durdurur ve bu dosya görüntüleme işlemi tamamlanana kadar diğer isteklere cevap vermez.
İşte bu durumda asenkron programlama devreye giriyor.
Burada asenkron programlama kullanırsak, dosya görüntüleme işlemi devam ederken bu işlemin bitmesini beklemeden diğer işleme bakabilir.
Asenkron programlamada önceden kullanılan Simple Thread Programming ile metotlar paralel olarak çalıştırabiliyordu. Ama bu durum işlemler tamamlanana kadar UI tarafında işlem yapmayı önlüyordu. Bunun önüne geçmek için de birçok kod yazılıyor ve bu da gereksiz maliyete neden oluyordu. Bunun önüne de Async-Await anahtar kelimeleri ile geçiyoruz.
Şimdi bir örnek ile devam edelim;
class Program
{
static void Main(string[] args)
{
Metot1();
Metot2();
Console.ReadKey();
}
public static async Task Metot1()
{
await Task.Run(() =>
{
for (int i = 0; i < 50; i++)
{
Console.WriteLine("Serhat");
// Do something
Task.Delay(200).Wait();
}
});
}
public static void Metot2()
{
for (int i = 0; i < 20; i++)
{
Console.WriteLine("Ayata");
// Do something
Task.Delay(200).Wait();
}
}
}
}
Yukarıda görüldüğü üzere Metot1 asenkron fakat Metot2 asenkron değildir. Şimdi bununla birlikte gelen sonucu görelim.

Burada görüldüğü üzere Metot1 metodu asenkrondur. Task.Run() metodunun çağrılmasıyla Metot1 metodunu çağıracak olan yeni bir Task kuyruğa eklenir. Thread Pool (iş parçacığı havuzu) boşta Thread varsa kuyruğa eklenen görev boştaki Thread üzerinde çalışmaya başlar. Bununla birlikte Metot1 metodu Main metodunun çalıştığı Thread‘den ayrılır ve asenkron bir şekilde çalışmaya devam eder.
Farklı bir örneğe bakalım;
Bu örnekte Metot1 toplam uzunluk değerini ve Metot3 parametresine de Metot1’den gelen uzunluk değerini gireceğiz.
class Program
{
static async Task Main(string[] args)
{
await MetotCagir();
Console.ReadKey();
}
public static async Task MetotCagir()
{
Metot2();
var sayi = await Metot1();
Metot3(sayi);
}
public static async Task<int> Metot1()
{
int sayi = 0;
await Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine(" Metot1");
sayi += 1;
}
});
return sayi;
}
public static void Metot2()
{
for (int i = 0; i < 25; i++)
{
Console.WriteLine(" Metot2");
}
}
public static void Metot3(int sayi)
{
Console.WriteLine("Toplam " + sayi);
}
}
}

Yukarıda gözükmüyor olsa da en başta 25 adet Metot2 yazısı çıkıyor. Ardından 100 adet Metot1 yazısı ve daha sonra ise toplam uzunluk değeri geliyor. Burada ilk olarak Metot2 tamamlanır, daha sonra Metot1 işlemi gerçekleşir ve bu sayi değişkenine atanır. En son olarak ta Metot3 metodu çalıştırılır. Yani gördüğümüz üzere işlemler asenkron şekilde çalışmaktadır.
.NET Framework’te bazı sınıflar asenkron programlamayı destekleyen metotlar içermektedir. Asenkron metotlar içeren bazı sınıflar HttpClient,SyndicationCli,ent,StorageFile,StreamReader vb. şeklinde sıralanabilir.
Örneğin bir Entity Framework konusunu düşünelim. Orada da ilk olarak Microsoft.EntityFrameworkCore paketini yükleriz ve daha sonrasında DbContext sınıfını kullanarak Migration işlemini gerçekleştiririz.
Burada veritabanı ile yapacağımız işlemleri asenkron olarak gerçekleştirmek istiyorum. Bir örnek vermek gerekirse, mesela oteller hakkında bilgileri veritabanında tuttuğumuzu düşünelim. Şimdi de yeni bir oteli veritabanına ekleyelim.
public async Task<Hotel> CreateHotel(Hotel hotel)
{
using (var HotelDbContext = new HotelDbContext())
{
await HotelDbContext.Hotels.AddAsync(hotel);
await HotelDbContext.SaveChangesAsync();
return hotel;
}
}
Yukarıda göreceğiniz üzere DbContext içerisinde yapacağım ekleme ve kaydetme işlemleri asenkron metotlar ile gerçekleştiriliyor. Senkron programlamada bu metotlar Add ve SaveChanges şeklindeydi ancak asenkron programlamada bu metotların asenkron hali olan AddAsync ve SaveChangesAsync kullanılmaktadır.
Aşağıdaki örnekte de bir dosyadan asenkron olarak değerleri okuyalım ve bunun üzerinde işlemler yapalım. Aşağıda filePath olarak verdiğim .txt uzantılı dosya içerisinde 12 karakterli bir metin yer alıyor. Farkettiğiniz üzere okuma işlemi de belli bir süre alacaktır.
class Program
{
static void Main()
{
Task task = new Task(CallMethod);
task.Start();
task.Wait();
Console.ReadLine();
}
static async void CallMethod()
{
string filePath = "C:\\Users\\Asus\\Desktop\\Deneme.txt";
Task<int> task = ReadFile(filePath);
Console.WriteLine("DenemeBaslangic1");
Console.WriteLine("DenemeBaslangic2");
int length = await task;
Console.WriteLine(" Toplam: " + length);
Console.WriteLine("DenemeSonu1");
Console.WriteLine("DenemeSonu2");
}
static async Task<int> ReadFile(string file)
{
int length = 0;
Console.WriteLine("Okuma işlemi başladı");
using (StreamReader reader = new StreamReader(file))
{
string s = await reader.ReadToEndAsync();
length = s.Length;
}
Console.WriteLine("Okuma işlemi tamamlandı");
return length;
}
}
Aşağıdaki kod bloğunda bekleme yapacaktır çünkü burada await anahtar sözcüğünü kullandık.
int length = await task;
Console.WriteLine("Toplam: " + length);
Ardından ise aşağıdaki kod bloğu çalışacaktır.
Console.WriteLine("DenemeSonu1");
Console.WriteLine("DenemeSonu2");

Burada en iyi anlaşılması gereken nokta, eğer await anahtar sözcüğü kullanmasaydık, metot senkron olarak çalışacaktı.