Arşiv

Etiketlenen yazılar tpl

Windows 8 Metro Style Uygulamalarda Async Desteği

17 Kasım 2011 Yorum yapılmamış

Windows 8 Programlama makale serisine, Async Desteği ile devam ediyoruz.

Asenkron programlama ölçeklendirilebilir ve yanıt veren (donmayan) uygulama geliştirmek için uzun yıllardır bilinen bir teknik. Uzun yıllardır bilinmesine rağmen, uygulama geliştiriciler olarak asenkron programlama bize hep zor gelmiştir.

.Net 4.5 ile birlikte asenkron programlama Async Framework sayesinde kolay programlanabilir hale getirildi ve Task Parallel Library‘nin (TPL) içerisine entegre edildi.

Asenkron programlama ile ilgili daha detaylı bilgiye, MSDN‘in Visual Studio Asynchronous Programming sayfasından ulaşabilirsiniz.

Bu makalede basit bir örnek ile async ve await anahtar kelimelerinin kullanımını inceleyeceğiz.

Yapacağımız örnek bir web sitesinin içeriğini okuyacak ve web adreslerini toplayacak. İlk olarak standart programlama yöntemlerini kullanarak uygulamamızı geliştireceğiz, daha sonra async ve await anahtar kelimeleri ile asenkron programlama öğelerini uygulamamıza dahil edeceğiz.

Hemen yeni bir Visual Studio 2011 açarak geliştirmeye başlayalım;

Yeni Proje oluşturma dialog kutusunda Windows Metro Style grubunda yer alan Application proje şablonunu seçelim ve projemize bir isim verelim (bu örnekte benim kullandığım isim, AsyncOrnek)

Proje oluşturulduğunda MainPage.xaml dosyasının içeriği;

<UserControl x:Class="Windows8ApplicationBar.MainPage"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	mc:Ignorable="d"
	d:DesignHeight="768" d:DesignWidth="1366">

	<Grid x:Name="LayoutRoot" Background="#FF0C0C0C">
	</Grid>

</UserControl>

İlk önce uygulamamıza bir TextBox, bir Button ve bir ListBox nesnesi ekliyoruz;

<Grid x:Name="LayoutRoot" Background="#FF0C0C0C">

	<TextBox x:Name="txtKaynakHtml" Width="600" Height="700" HorizontalAlignment="Left" />
	<ListBox x:Name="lbUrlList" Width="600" Height="700" HorizontalAlignment="Right" />
	<Button x:Name="btnTara" Content="Tara" HorizontalAlignment="Center" />

</Grid>

Böylece uygulama ekranımız şu şekilde gözüküyor;

TextBox kontrolünün Text özelliğine web sitesinin kaynak html’ini atayacağımız için, çok satır gösterebilir olması lazım (MultiLine)

WPF TextBox kontrolünün MultiLine olabilmesi için, TextBox element’ine şu özellikleri ekliyoruz;

TextWrapping="Wrap" AcceptsReturn="True" ScrollViewer.VerticalScrollBarVisibility="Visible"

Button kontrolüne basıldığında tarama işlemini başlatmak için, Button element’inde Click olayını yakalıyoruz;

Click="btnTara_Click"

Böylece uygulamamızın XAML kod’u aşağıdaki hale gelmiş oluyor;

<UserControl x:Class="AsyncOrnek.MainPage"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	mc:Ignorable="d"
	d:DesignHeight="768" d:DesignWidth="1366">

	<Grid x:Name="LayoutRoot" Background="#FF0C0C0C">

		<TextBox x:Name="txtKaynakHtml" Width="600" Height="700" HorizontalAlignment="Left" TextWrapping="Wrap" AcceptsReturn="True" ScrollViewer.VerticalScrollBarVisibility="Visible" />
		<ListBox x:Name="lbUrlList" Width="600" Height="700" HorizontalAlignment="Right" />
		<Button x:Name="btnTara" Content="Tara" HorizontalAlignment="Center" Click="btnTara_Click" />

	</Grid>

</UserControl>

MainPage.xaml.cs dosyasında btnTara_Click method’unu yazıyoruz;

private void btnTara_Click(object sender, RoutedEventArgs e)
{
}

Öncelikle, taradığımız sitede bulacağımız url’leri saklayacağımız string dizisini oluşturalım;

List<string> UrlList = new List<string>();

HttpClient sınıfından yeni bir instance oluşturup, Get() method’una, tarayacağımız sitenin adresini yazalım.

Get() method’undan dönen HttpResponseMessage tipindeki cevabı bir değişkene atayalım;

var HttpCagri = new HttpClient().Get("http://www.enginpolat.com");

HttpClient sınıfı System.Net.Http namespace‘inde yer aldığı için, MainPage.xaml.cs dosyasının using kısmına

using System.Net.Http;

satırını ekleyelim.

HttpCagri değişkeninin sitenin kaynak html’ini okuyabildiğinden emin olmak için, EnsureSuccessStatusCode() method’unu çağıralım.

Böylece HttpContent tipindeki Content özelliğine erişebilir olacağız. Content özelliğinin ReadAsString() method’u kaynak html’ini döndürecek, biz de bir değişken aracılığıyla bu html’i saklayacağız.

var KaynakHtml = HttpCagri.EnsureSuccessStatusCode().Content.ReadAsString();

Elimizdeki bu html verisini, arayüzdeki txtKaynakHtml TextBox‘ında göstermek için;

txtKaynakHtml.Text = KaynakHtml;

Şimdi yapmamız gereken, KaynakHtml değişkenindeki url‘leri bulmak. Bunun için Regex sınıfından faydalanacağız ve yazacağımız Regular Expression‘a uyan url’leri UrlList değişkeninde biriktireceğiz.

Regex sınıfı System.Text.RegularExpressions namespace’inde olduğu için, MainPage.xaml.cs dosyasının using kısmına

using System.Text.RegularExpressions;

satırını ekleyelim.

Öncelikle Regex sınıfının static Matches method’una KaynakHtml değişkenini, Regular Expression metnini ve arama yaparken büyük/küçük harf ayrımı yapmaması için RegexOptions.IgnoreCase değerini parametre olarak veriyoruz.

RegexOptions parametresine verebileceğimiz diğer değerler;

  • RegexOptions.None
  • RegexOptions.IgnoreCase
  • RegexOptions.Multiline
  • RegexOptions.ExplicitCapture
  • RegexOptions.Compiled
  • RegexOptions.Singleline
  • RegexOptions.IgnorePatternWhitespace
  • RegexOptions.RightToLeft
  • RegexOptions.ECMAScript
  • RegexOptions.CultureInvariant

Method’dan dönen MatchCollection tipindeki cevabı bir değişkende saklıyoruz;

var mc = Regex.Matches(KaynakHtml, "href\\s*=\\s*(?:\"(?<1>http://[^\"]*)\")", RegexOptions.IgnoreCase);

Bir foreach döngüsü ile MatchCollection‘daki her Match‘i UrlList‘e ekliyoruz;

foreach (Match m in mc)
{
	UrlList.Add(m.Groups[1].Value);
}

Son olarak arayüzdeki lbUrlList ListBox‘ında bu listeyi gösteriyoruz;

lbUrlList.ItemsSource = UrlList;

İlk çalıştırma aşamasına geldik. Projeyi çalıştırıp Tara butonuna bastığımızda garip bir hata alıyoruz;

Cannot write more bytes to the buffer than the configured maximum buffer size: 65536

Bu hataya, kaynak html’ini aldığımız siteden dönen cevabın 65536 karakterden büyük olması yol açıyor. Çözüm aslında basit;

HttpClient tipindeki değişkenin MaxResponseContentBufferSize özelliğine sitenin kaynak html’inin karakter sayısı kadar büyük bir değer vermemiz lazım.

Bu örnek için ben verilebilecek en büyük değer olan int.MaxValue değerini seçtim, HttpCagri değişkenini tanımladığımız satırı şöyle güncelliyoruz;

var HttpCagri = new HttpClient() { MaxResponseContentBufferSize = int.MaxValue }.Get("http://www.enginpolat.com");

Artık projemizi çalıştırabiliriz;

Buraya kadar uygulamayı çalıştırmaya odaklanmıştık, uygulamayı çalıştırdıktan sonra kullanıyoruz ve farkediyoruz ki, Tara butonuna bastıktan sonra liste dolana kadar uygulama kullanıcıya yanıt vermiyor.

Kullanıcı dostu bir uygulamanın her zaman kullanıcıya yanıt vermesi beklenir. Biz de Tara butonunun yaptığı işi asenkron yapmasını sağlayarak oluşan donmaların önüne geçebiliriz.

Asenkron çalışma yeteneğini eklemek için öncelikle btnTara_Click method’una async anahtar kelimesini ekleyeceğiz;

private async void btnTara_Click(object sender, RoutedEventArgs e)

Uygulamanın donmasına sebep olan en uzun iş, internet sitesinin cevabının alındığı HttpClient sınıfının Get() method’u. Bu yüzden Get() method’unu asenkron özellikte olan GetAsync() method’u ile değiştiriyoruz.

HttpCagri değişkenine değer atamadan önce await anahtar kelimesi ile asenkron yaptığımız çağrının sonuçlanmasını beklemeliyiz. İlgili satırı aşağıdaki şekilde güncelleyelim;

var HttpCagri = await new HttpClient() { MaxResponseContentBufferSize = int.MaxValue }.GetAsync("http://www.enginpolat.com");

Böylece uygulamamıza asenkron çalışabilme yeteneğini kazandırmış olduk. Projenin kodlarının tamamı;

<UserControl x:Class="AsyncOrnek.MainPage"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	mc:Ignorable="d"
	d:DesignHeight="768" d:DesignWidth="1366">

	<Grid x:Name="LayoutRoot" Background="#FF0C0C0C">

		<TextBox x:Name="txtKaynakHtml" Width="600" Height="700" HorizontalAlignment="Left" TextWrapping="Wrap" AcceptsReturn="True" ScrollViewer.VerticalScrollBarVisibility="Visible" />
		<ListBox x:Name="lbUrlList" Width="600" Height="700" HorizontalAlignment="Right" />
		<Button x:Name="btnTara" Content="Tara" HorizontalAlignment="Center" Click="btnTara_Click" />

	</Grid>

</UserControl>
private async void btnTara_Click(object sender, RoutedEventArgs e)
{
	List<string> UrlList = new List<string>();

	var HttpCagri = await new HttpClient() { MaxResponseContentBufferSize = int.MaxValue }.GetAsync("http://www.enginpolat.com");
	var KaynakHtml = HttpCagri.EnsureSuccessStatusCode().Content.ReadAsString();

	txtKaynakHtml.Text = KaynakHtml;

	var mc = Regex.Matches(KaynakHtml, "href\\s*=\\s*(?:\"(?<1>http://[^\"]*)\")", RegexOptions.IgnoreCase);

	foreach (Match m in mc)
	{
		UrlList.Add(m.Groups[1].Value);
	}

	lbUrlList.ItemsSource = UrlList;
}

Şubat 2010 Seminerleri / C# 4.0 Yenilikleri – 2

19 Şubat 2010 2 yorum

BilgeAdam Kadıköy Şubesinde gerçekleştirdiğim Şubat Seminerlerinin ikincisinin prezentasyonuna ve proje kodlarına buradan erişebilirsiniz.

Bu seminerde değindiğim konular;

  • Dynamics (DLR)
  • Lazy Sınıfı
  • COM İyileştirmeleri
  • Task Parallel Library (TPL)
  • ASP.NET 4.0 Yenilikleri
  • SEO İyileştirmeleri
  • Dynamic Data Web Site

Powerpoint Prezentasyonu

Visual Studio 2010 Kodları

Normal For ve Paralel For Döngülerinin Karşılaştırması

08 Aralık 2009 Yorum yapılmamış

Yazdığımız uygulamaların olmazsa-olmaz’larından birisi de, for döngüleridir.

Ne kadar basit’te olsa, ne kadar karmaşık’ta olsa, mutlaka uygulamalarımızda for döngülerine ihtiyaç duyarız ve sıklıkla kullanırız.

Bu makaleyi okumaya devam etmeden önce, .Net Framework 4.0 ile birlikte gelecek olan Task Parallel Library hakkında yazdığım şu yazıyı (TPL (Task Parallel Library) – Task Class) okumanızı tavsiye ederim.

Task Parallel Library ile gelen Parallel sınıfının For methodunu kullanarak, Multi-Core destekli for döngüleri oluşturabiliriz.

Multi-Core destekli olması, Multi-CPU (birden çok CPU içeren) bilgisayarlarda, döngünün CPU’lara dağıtılabilmesini sağlıyor.

Parallel Library‘den önce for döngüsü;

//10 adımlı döngü (0 dahil, 10 hariç)
for (int iLoop = 0; iLoop < 100; iLoop++)
{
	// Yapılacak İş
}

Parallel Library ile for döngüsü;

//10 adımlı döngü (0 dahil, 10 hariç)
Parallel.For(0, 10, iLoop => {
	// Yapılacak İş
});

Parallel.For metodunu kullarak örnek bir proje hazırladım, projenin kaynak kodlarına buradan ulaşabilirsiniz.

NormalFor_ParallelFor_1

Uzun tek bir döngü yazmak yerine, içiçe iki döngü yazdım, böylece, CPU ve İşletim Sistemi’nin çok adımlı döngülerde yaptığı iyileştirmeleri bir miktar azaltmayı ve daha doğru bir sonuç elde etmeyi amaçladım.

Döngülerden bir tanesini gene de uzun tuttum (100.000.000 – Yüz Milyon Adım), böylece, eğer Framework’ün kendisi döngülerde iyileştirme yapıyorsa, bunları yakalamayı hedefledim.

Diğer döngüyü ise nispeten daha kısa (500 – Beş Yüz) tuttum.

NormalFor_ParallelFor_2

Böylece toplamda 50.000.000 (Elli Milyar) adımlı döngü oluşmuş oluyor.

Gelelim, kodlara;

private void btnNormalDongu_Click(object sender, EventArgs e)
{
	int Sayac = 0;
	Stopwatch sw = Stopwatch.StartNew();
	for (int iLoop = 0; iLoop < (int)txtTekrarAdedi.Value; iLoop++)
	{
		for (int yLoop = 0; yLoop < (int)txtDonguAdedi.Value; yLoop++)
		{
			Sayac++;
		}
	}
	sw.Stop();

	lblNormalDonguSonuc.Text = string.Format("{0:0,0} ms.", sw.ElapsedMilliseconds);
}

ve

private void btnParalelDongu_Click(object sender, EventArgs e)
{
	int Sayac = 0;
	Stopwatch sw = Stopwatch.StartNew();
	Parallel.For(0, (int)txtTekrarAdedi.Value, i => {
		Parallel.For(0, (int)txtDonguAdedi.Value, y => {
			Sayac++;
		});
	});
	sw.Stop();

	lblParalelDonguSonuc.Text = string.Format("{0:0,0} ms.", sw.ElapsedMilliseconds);
}

Teker teker butonlara bastığımızda, for döngüleri çalışacak ve sonuçları Label’larda göreceğiz.

Uygulamayı kendi bilgisayarımda çalıştırdığımda aldığım sonuçlar;

NormalFor_ParallelFor_3

Normal for döngüsü: 888.700 ms. (yaklaşık 14 dakika 48 saniye)

Paralel for döngüsü: 219.326 ms. (yaklaşık 3 dakika 39 saniye)

Aradaki fark: 75%

Uygulamayı çalıştırdığım bilgisayarın özellikleri;

NormalFor_ParallelFor_4

TPL (Task Parallel Library) – Task Class

06 Aralık 2009 Yorum yapılmamış

.Net 4.0 ile birlikte gelecek güzel class’lardan bir tanesi de Task class’ı.

Task class’ı ve TPL (Task Parallel Library) ile birlikte gelen diğer class’lar sayesinde paralel programlama .net framework ile desteklenir hale geliyor.

TPL_Task_1

Yandaki görselde görebileceğiniz gibi, TPL (Task Parallel Library) ve PLINQ (Parallel LINQ) .Net Framework 4.0 ile gelen en önemli özellikler.

Paralel programlamanın performans’a katkısını MSDN’de yer alan şu iki makale’de okuyabilirsiniz;

Parallel Programming in .Net Framework

Parallel Performance

Gene MSDN’de yer alan şu sayfalardan da .net 4 ile gelecek yeni yapıları öğrenebilirsiniz;

Data Structures for Parallel Programming

Task Parallelism (Task Parallel Library)

Introduction to Tasks

Task class’ını kullanarak, Thread yönetme maliyetlerinden kurtularak kolaylıkla Multi-Core uyumlu, Asenkron çalışabilen kodlar yazabiliriz.

Task class’ından yeni bir örnek oluşturmak için;

Aşağıdaki gibi yeni bir instance oluşturup, daha sonra Start method’u ile başlatabiliriz.

Task t0 = new Task(() =>
	MessageBox.Show("Task Örneği 1")
);
t0.Start();

Veya, aşağıdaki kodda yazdığım gibi, Task class’ının singleton static Factory property’sinin StartNew method’u ile hem tanımlayıp, aynı anda başlatabiliriz.

Task t1 = Task.Factory.StartNew(() =>
{
	string Mesaj = "Birinci çalışacak..";
	MessageBox.Show(Mesaj);
});

Örneğini oluşturduğumuz Task nesnesi, başlatılana kadar hafızada bekler. Başlatıldığında, ayrı bir thread’de çalışacağından dolayı, uzun süren işlerin arayüz’ü kilitlemesini önlemek için kullanılabilir.

Birden Fazla Task’ın peşpeşe çalışması sağlanabilir;

Task t2 = Task.Factory.StartNew(() =>
{
	MessageBox.Show("Birinci çalışacak..");
}).ContinueWith((t) =>
{
	MessageBox.Show("İkinci çalışacak..");
}).ContinueWith((t) =>
{
	MessageBox.Show("Üçüncü çalışacak..");
});

Veya birden fazla Task’ın aynı anda çalışması da sağlanabilir;

Task t3 = Task.Factory.StartNew(() =>
{
	Task c1 = Task.Factory.StartNew(() =>
	{
		MessageBox.Show("Aynı anda çalışacak..");
	}, TaskCreationOptions.AttachedToParent);

	Task c2 = Task.Factory.StartNew(() =>
	{
		MessageBox.Show("Aynı anda çalışacak..");
	}, TaskCreationOptions.AttachedToParent);
});

TaskCreationOptions parametresine verdiğimiz AttachedToParent değerine dikkat!.. Bu sayede c1 ve c2 Task’ları t3 Task’ının Child Task‘ları haline geldi, t3 Task’ı da Parent Task oldu.