MVC EntityFramework Asenkron Programlama
Selamlar;
Bugün Mvc’de asenkron ve multi thread işlemler üzerine konuşacağız.
Diyelimki bir cafe’de kasada çalışan bir görevli olsun. Müşteriden tost siparişi geldi. Tost makinaya kondu ve bekleniyor. Bu bekleme zamanında eğer görevli yeni sipariş almaz ise yani işlemi bekler ise sıra dolar taşar.Yoğunluk çok artar. Doğru olan görevlinin tost olana kadar sipariş almaya devam etmesi ve tost bitince makinadan bir uyarı ile görevlinin sipariş alma işlemine ara verip tostu müşteriye vermesi ve kaldığı işe devam etmesidir. İşte asenkron programlama tam bu mantık üzerine çalışmaktadır. Bir request geldiği zaman thread pool’daki işlemci bu istek ile ilgilenmeye başlar.Mesela bir websitesinin string olarak download edilmesi istensin. Eğer işlem uzun sürecek ise bu esnada thread pool’daki thread gelen yeni request’e cevap verecektir. Download işlemi bitince process’den bir çağrı gelir ve thread önceki requestdeki işlemine kaldığı yerden devam eder. Mesela indirilen websitesindeki tüm linkleri alıp ön tarafta listelemesi gibi.
Aşağıda görüldüğü gibi üç çeşit async programlama modeli vardır.Biz tabi bu makalede Task-based asynch modeli üzerine konuşacağız.
Örnek amaçlı günlük kurları alan aşağıda görüldüğü gbi asenkron bir uygulama yazalım.
1 2 3 4 5 6 |
public async Task<string> GetCurrencies() { WebClient client = new WebClient(); var exchange = await client.DownloadStringTaskAsync("http://www.tcmb.gov.tr/kurlar/today.html"); return exchange; } |
Öncelikle yukarıda methodun başında async Task kullanılmıştır.Burada methodun asenkron olduğu ve birtane task tipinde dönüş yapılacağı,sonucun da string içerdiği belirtilmiştir.
Ayrıca WebClient’ın DownloadStringTaskAsync şeklinde asenkron çalışan bir method’u vardır. Başına’da await kelimesi konmuştur. WebClient’dan bir request’de bulunulduğunda, işlem bitene kadar alt satıra inmeden beklenmesini await kelimesi ile belirtilir. İşlem yapılırken WebClient’ın string’i download etmesi kısmını bu düzenden ayırıp kendi başına çalışmasını sağlayan işlem bitince de geribildirimde bulunun kısım methodun başına konan async ifadesidir. Bu sayede işlem yoğunluğundan dolayı herhangi bir blocklanma olmamaktadır. Method dönüş tipi olarak Task döndürmektedir.
Şimdi de database’den ürünleri async olarak çekelim. Bu sayede çekilen datanın fazalalığından veya connection ile ilgili sürenin uzamasından dolayı herhangi bir blocklanma ile karşılaşılmaz.
Aşağıda görüldüğü gibi ToListAsync ile linq’in async özelliğinden faydalanılmıştır. Entity 6.0 ve üzeri linq’de async desteklemektedir. Product tablosundan async olarak data çekilmekte gene await ile işlemin bitmesi beklenmekte ve method başına konan async Task ile işlemin asenkron olarak yapılması ve dönüş tipinin Task olduğu belirtilmiştir. Sonuç List<tblProduct> içermektedir.
1 2 3 4 5 6 7 8 9 |
public async Task<List<tblProduct>> GetProduct() { var products = new List<tblProduct>(); using (JewelleryStoreDB dbContext = new JewelleryStoreDB()) { products = await (from data in dbContext.tblProduct orderby data.Name select data).ToListAsync(); return products; } } |
Bir de Log tablosundan dataları async olarak aşağıda görüldüğü gibi çekelim.
1 2 3 4 5 6 7 8 9 |
public async Task<List<tblLog>> GetLog() { var log = new List<tblLog>(); using (JewelleryStoreDB dbContext = new JewelleryStoreDB()) { log = await (from data in dbContext.tblLog.Include(tl=>tl.tblUser) orderby data.ID descending select data).ToListAsync(); return log; } } |
Şimdi HomeController’ımızda Index methodumuz’da yukarıda belirtilen 3 farklı datayı çekip View Modelimizi dolduralım. İlgili modelimiz aşağıda belirtilmiştir.
1 2 3 4 5 6 7 8 |
public class ModelData { public Task<List<tblProduct>> product { get; set; } public Task<List<tblLog>> log { get; set; } public Task<string> exchange { get; set; } public DateTime StartTime; public DateTime EndTime; } |
Şimdi de AsyncController’dan türeyen HomeController’ımızı oluşturalım.
Aşağıda görüldüğü gibi async Tasck<ActionResult > döndüren bir Index Methodumuz vardır. Asenkron olarak Task tipinde ActionResult döndüren bir methoddur. Üç Task’ımıza request yapıldıktan sonra await Task.WhenAll methodu ile tüm taskların tamamlanması beklenir. Tüm tasklar bitince ilgili model doldurulup Index View model ile birlikte döndürülür. Bu belirtilen üç task paralel olarak çalışmaktadır. Ayrıca sistemi block’e etmeden asenkron olarak arkada sistemden bağımsız olarak kendilerinden istenen işleri yaparlar. Taskler tamamlandıkça bittiklerine dağir callback yaparlar. Tüm tasklar bittikten sonra await ile bekletilen kısım atlatılıp bir sonraki yani ilgili modelin doldurulması ve view’ın döndürülmesi işine geçilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class HomeController : AsyncController { // GET: Home public async Task<ActionResult> Index() { ModelData mdl = new ModelData(); mdl.StartTime = DateTime.Now; var currnecyData = GetCurrencies(); var product = GetProduct(); var log = GetLog(); await Task.WhenAll(currnecyData,product, log); mdl.EndTime = DateTime.Now; mdl.log = log; mdl.product = product; mdl.exchange = currnecyData; return View(mdl); } |
Index View :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
@model TaskAsync.Controllers.ModelData @{ ViewBag.Title = "Index"; } Başlangç: @(Model.StartTime) </br> Bitiş: @(Model.EndTime) </br> <table border="1"> <tr style="vertical-align: top;"> <td style="vertical-align: top; width:20%;"> <div>@Html.Raw(Model.exchange.Result)</div> </td> <td style="vertical-align: top;width:20%"> <div> <table> <tr style="vertical-align: top;"><td>Name</td><td>İmageUrl</td><td>Price</td><td>Description</td><td>Date</td></tr> @foreach (var item in Model.product.Result) { <tr> <td>@item.Name</td> <td>@item.ImageUrl</td> <td>@item.Price</td> <td>@item.Description</td> <td>@item.CreatedDate</td> </tr> } </table> </div> </td> <td style="vertical-align: top;width:20%"> <div> <table> <tr style="vertical-align: top;"><td>Name</td><td>Log Tipi</td><td>Date</td></tr> @foreach (var item in Model.log.Result) { <tr> <td>@item.tblUser.Name</td> <td>@item.LogType.ToString()</td> <td>@item.CreatedDate</td> </tr> } </table> </div> </td> </tr> </table> |
Ekran Çıktısı:
Yukarıda solda kurlar, ortada ürün dataları ve en sağda log datalarının bilgisi eş zamanlı çekilip ekran gösterilmesi sağlanmıştır. Toplam işlem süresi 5 sndir.Sisteme yük bindirdikçe yani anlık request binleri bulsa bile süre asenkron yükleme nedeni ile hem çok değişmeyecek hemde sistem bloke olmayacaktır.
Şimdi bir test yapalım.Uygulamamızı senkron olarak alttaki gibi yazalım:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class HomeController : Controller { // GET: Home public async Task<ActionResult> Index() { ModelData mdl = new ModelData(); mdl.StartTime = DateTime.Now; var products = new List<tblProduct>(); using (JewelleryStoreDB dbContext = new JewelleryStoreDB()) { products= (from data in dbContext.tblProduct orderby data.Name select data).ToList(); } var log = new List<tblLog>(); using (JewelleryStoreDB dbContext = new JewelleryStoreDB()) { log = (from data in dbContext.tblLog.Include(tl => tl.tblUser) orderby data.ID descending select data).ToList(); } WebClient client = new WebClient(); var exchange = client.DownloadString("http://www.tcmb.gov.tr/kurlar/today.html"); mdl.EndTime = DateTime.Now; mdl.log = log; mdl.product = products; mdl.exchange = exchange; return View(mdl); } |
Şimdi Apache Bench ile yani ab.exe ile load test yapıcaz. Apache sunucu testidir. Makinada IIS kurulu ise Apache kurmanıza gerek yoktur.
ab -n 100 -c 10 http://www.yahoo.com/
Kullanım şekli yukardaki gibidir -n 100 user sayısını -c 10 concurrency yani herbir user için request sayısını belirtmektedir. En sonda da load test yapılacak url belirlenir.
Aşağıda 500 user ve 100 concurrency ile yukarıdaki senkron uygulama load test’e tabi tutuldu.9.981 sn toplam sürede en uzun request 7.563 sn sürdü.
Aynı test asenkron uygulama için 500 user ve 100 concurrency ile aşağıdaki gibi çalıştırılmıştır.6.808 sn toplam sürede en uzun request 2.229 sn sürmüştür.
Yukarıdaki load testde de görüldüğü gibi senkron ve asenkron uygulamalar arasındaki performans farkı yük artışı ile büyük değişiklik göstermektedir.
Geldik bir makalenin daha sonuna yeni bir makalede görüşmek üzere hoşçakalın.
Source: https://channel9.msdn.com/Events/TechDays/Techdays-2012-the-Netherlands/2287
Çok güzel, açık, akıcı bir makale olmuş elinize sağlık. Başarılarınızın ve makalelerinizin devamını dilerim.
İyi akşamlar.
Teşekkürler Enver;
İşine yaradı ise ne ala…
Güzel ve açıklayıcı bir yazı, emeğinize sağlık.
Tesekkurler Gokhan.
Isine yaradi ise ne mutlu bana
Öncelikle bu değerli yazı için teşekkür ederim ellerinize sağlık. Aklıma takılan bir soru var;
Her metotu Task olarak tanımlamamız daha mı doğru olur? Ya da Task olarak kullanımı ne zaman tercih etmeliyiz?
Selam Mustafa;
Öncelikle teşekkürler;
Her method() kesinlikle her zaman Asenkron olarak TANIMLANMAZ! Eğer yapılacak iş uzun bir zaman alacak ve sistem gerçekten yoğun ise yani bu arada cevap vermesi gereken başka requestler var ise tabiki yeterli bir memory ve Cpu ile Task methodlar tercih edilebilir. Unutma ki her getirinin bir de götürüsü vardır!
Teşekkür ederim hocam.
Bora abi bir siteyi komple tüm içeriği web api den gelecek şekilde yazdığımızı düşünelim. Burada her veri çekecek action u asenkron mu yapmalıyız. Mesela kullanıcı ana sayfada hem ürünleri hem sepeti hemde menüyü görecek aynı anda 3 defa api den veri gelecek. Ayrıca api i de asenkron mu yazmalıyız
Bir WebApiden alıcaksan asenkron yerine tek bir model ile dönebilirsin. Yani Bir sınıfın var Container ve 3 propertysi var. Onlar da birer sınıf. Product,Basket and Menu şeklinde bir json geri dönüş yapabilirsin.ç Hatta servisi Cors’a açık yap ve Web’de client side’dan çekip doldur.
İyi çalışmalar.
Burak Hocam elinize sağlık,
Sorum şu olacak asenkron kullandığımız yerde controller AsyncController mı olacak her zaman?
Selam Sinan,
İsim önemli değil. Zaten sitemden yazıyorsun :) Evet herzaman Async olmalı. Bunun en zor yanı, consol applicationlarda Main() methodu Async olarak işaretlenemez. Peki bu nasıl olacak. Araştır. Yolu var. İhtiyacın olur bulamassan sor gene:)
iyi çalışmalar.
Burak yazdım kusura bakmayın o sırada burak arkadaşımla konuşuyordum :)
Hocam aklıma bir şey takıldı.
public async Task Test()
{
int userId = GetUserId();
var products = await GetProducts(userId); //Database
//OTHER
//category vs..
return Ok(“”);
}
böyle bir metotum var diyelim. Aynı thread da 2 istek bu metota geldi ( 2 farklı kullanıcı olarak 1 ve 2 id li). 1 idli kullanıcıda await kısmında beklemeye geçti ve 2 idli kullanıcı için bu metota geldi. Aklıma takılan bir istek başka bir isteğe müdale ediyor mu asenkron durumda yani kullanıcı 1’in isteğinde awaitten sonra userId kaç olacak yine 1 mi olacak yoksa 2 numaralı istekde oraya geldiği için 2 olarak mı gelecek. Biraz karmaşık oldu ama umarım anlatabilmişimdir.
Selam Cihan,
userId hem 1 olucak hem 2 olucak. Hangisinin önce olucağı da belli değil :) 2 client farklı threadlerde çalışacaktır. Ben yerinde olsam bu tarz yapılarda senkron çalışırdım. Asenkron yapıları “push and forget” yani at ve unut yapılarda kullanırdım. Mesela bir video render edilmesi. Ya da bir webservices’den get işleminin yapılması gibi.
İyi çalışmalar.
Cevabınız için teşekkür ederim. Peki neden senkron olarak yapmalıyım, tam olarak neden senkron olarak yapmamı öneriyorsunuz.
Merhaba AsyncController farkı nedir kısaca anlatabilirmisiniz ?
Teşekkürler,
İyi çalışmalar.
Hocam emeğinize sağlık çok açık bir anlatım olmuş.
Teşekkürler Ömer..