تقييم الموضوع :
  • 0 أصوات - بمعدل 0
  • 1
  • 2
  • 3
  • 4
  • 5
Deep Threading in .net
#1
كاتب الموضوع : SHADY_20075

بسم الله الرحمن الرحيم

انا اسمى عبدالله مبرمج مبتدأ وعلى قدى

احب اقدملكم درس من دروس التى تناولتها من الأستاذ المهندس (محمد الشريف)
مع جزيل الشكر له والتقدير
واسالكم صالحه له ولى

فالنبدأ
هل جربت من قبل أن تتعامل مع الThreads ؟؟؟؟
اذا كانت اجابتك بلا فاقرأ معي هذا الموضوع و اذا كانت اجابتك بنعم فقرأه أيضاً ففيه فائدة عظيمة ستجدها بين سطور الموضوع

هل ترى التعامل مع الThreads صعب ؟؟؟؟
هو ليس صعباً و لكنه فقط مرحلة أخرى متقدمة و لا يجب أن تكون كل مرحلة متقدمة صعبة و العكس صحيح فأحيانا البداية تكون الأصعب على الاطلاق

ما هي الThreads ؟
الThreads هي محاولة تجزئة البرنامج الى مجموعة من العمليات المستقلة و التي يمكن أداءها بطريقة متوازية Parallel بدلاً من اضاعة الوقت في انتظار لا طائل منه و هو النظام المتسلسل Serial Execution و يهدف النظام الى تحسين أداء البرامج عموماً فاذا تخيلنا مثلاً برنامجأ رياضياً يقوم بعمل حسابات طويلة و معقدة (Matlab مثلاً) فان البرنامج أثناء تنفيذ هذه العمليات لا يمكنه اداء اي مهمة اخرى حتى ينتهي من الحسابات و من مساوئ هذا هو عدم استجابة البرنامج الى المستخدم (بالبلدي الشاشة بتهنج)...

و لكن هل هذا مانراه فعلاً في البرامج الحقيقية؟؟؟
بالطبع لا فبرنامج Matlab مثلاً يمكتك من تشغيل برامجك و كتابة برامج أخرى و التفاعل مع واجهاته في نفس الوقت بدون أي مشاكل و هذا لأن جميع هذه العمليات هي عمليات مستقلة يمكن التعامل معها من خلال البرنامج بطريقة متوازية

هل الThreading مفهوم جديد
الThreading بصفة عامة مفهوم معروف و لكن للأسف لم يتم تطبيقه على مستوى لغات البرمجة بل تُرك مفتوحاً لكل نظام يطبقه كما يشاء و يدعم ما يريد و هذا ما جعل الThreading في ال++C مثلاً من المواضيع السيئة فكل نظام له الAPI's الخاصة به و التي تختلف كلياً عن أي نظام اخر...
و لكن مع ظهور الأجيال الحديثة من لغات البرمجة بدأً من الJAVA و التي حسب معلوماتي أول لغة ظهر الThreading فيها كجزء من اللغة نفسها بحيث أنتقلت مشاكل التعامل مع النظام و طريقة تطبيق الThreading نفسها الى الJVM بدلاً من المبرمج المسكين...
و بالطبع مع ظهور ال.net كان الThreading جزء لا يتجزأ من الFramework و يمكن استخدامه من أي لغة تتبع الCLR ..

اذاً كيف نقوم بعمل Threading في ال#C ؟؟؟
أولاً هناك Namespace تختص بالThreading و هي System.Threading و هي تحتوي على جميع الclasses الخاصة بالThreading....
اذاً فأول سطر في برنامج يتعامل مع الThreading هو
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->

CODE

using System.Threading


أهم Class في الThreading بالطبع هي System.Threading.Thread و هي التي توجد فيها أغلب العمليات الأساسية
اذاً هل الخطوة الثانية هي انشاء object من نوع Thread ؟؟
الحقيقة هذه الخطوة تسبقها خطوة قبلها و هي كتابة الfunction التي ستقوم هذه الThread بتنفيذها و لتكن مثلاً
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->

CODE

void PrintHello()
{
Console.WriteLine("Hello Threading...");
}


ثم نقوم بعمل الObject من نوع Thread
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->

CODE

Thread thread = new Thread(new ThreadStart(PrintHello));


ثانية واحدة ايه اللي انت كاتبه ده ؟؟؟؟ مين ThreadStart دي و ازاي كاتب اسم الfunction كأنه parameter ؟؟؟؟
لو سألت نفسك السؤال السابق اذن فأنت أول مرة تتعامل مع الDelegates و هي باختصار شديد بديل محترم لما كنا نستخدمه في ال++C/C و هو الFunction Pointer ...
الحقيقة المجال لا يتسع لشرح الDelegate هنا و لكن يمكنك أن ترجع للMSDN أو ابحث في المنتدى... و لو مستعجل كل اللي محتاج تعرفه في السطر اللي فات اني بقول للThread بتاعتي ان الfunction اللي حتنفذها اسمها PrintHello
شفت خرجتني من الموضوع ازاي؟؟؟ <!--[if !vml]--><!--[endif]-->على العموم ما علينا

بقيت خطوة أخرى و هي تشغيل الThread نفسها باضافة سطر اخر
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->

CODE

thread.Start();



هي دي الThreads بقة و عاملين الهوليلا دي على ال 3 سطور دول؟
لأ طبعاً دي البداية بس الموضوع طويل و فيه كلام كبير صحصح معايا كده لسة التقيل ورا

طيب هل هذه هي الطريقة الوحيدة لانشاء Threads

الاجابة بالطبع لا و لكن توجد طرق أخرى كثيرة و هناك أيضاً طرق يتم فيها انشاء Threads بواسطة النظام دون أن يدخل المبرمج في تفاصيل انشاء الThreads و هي ما يسمى بالAsynchronous Operations و ستجد الكثير منها في الdotnet فمبدئيأ أي عملية تبدأ بكلمة BeginْXXX و يوجد لها مثيل يبدأ بEndXXX تعتبر عمليات غير متزامنه و مثال على هذا في الStreams ستجد BeginRead و EndRead و أيضاً BeginWrite و EndWrite و هذا موضوع اخر مثير في الdotnet و سأحاول شرحه لاحقاً
و يوجد أيضاً طريقة أخرى لانشاء الThreads و تعتمد على فكرة أن انشاء Thread جديدة و تشغيلها و مايتطلبه هذا من بعض العمليات الأخرى أحياناً يكون وقت هذه العمليات أكبر من العملية التي ستنفذها الThread في النهاية مما يؤثر سلباً على الأداء و هنا ظهر مفهوم الThreadPool أي مستودع الThreads و هي مجموعة من الThreads المنشأة (بضم الميم) مسبقاً و هي جاهزة و تحتاج فقط للعملية التي ستقوم بها و يكون لها عدد مُحدد و يمكن للمستخدم أن يستخدمها ببساطة جداً
طريقة استخدامها تكون من الThreadPool Class عن طريق وضع الfunction المُراد تنفيذها في الQueue حتى تفرغ احدى الThreads لتنفيذ هذه العملية و يكون هكذا
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->

CODE

ThreadPool.QueueUserWorkItem(new WaitCallback(PrintHello));


و هكذا سيتم وضع العملية في الQueue و سيكمل البرنامج عمله دون انتظار و عندما تكون هناك Thread جاهزة سيتم التنفيذ


مشكلة كبيرة جداً تواجه البرامج التي تعمل فيها أكثر من Thread و هي مشكلة ال Data Race أو التسابق...
شرح هذه المشكلة بسيط
انظر معي الى هذا الكود
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->

CODE

using System;
using System.Threading;

public class Test
{
static int count=0;

static void Main()
{
ThreadStart job = new ThreadStart(ThreadJob);
Thread thread = new Thread(job);
thread.Start();

for (int i=0; i < 5; i++)
{
count++;
}

thread.Join();
Console.WriteLine ("Final count: {0}", count);
}

static void ThreadJob()
{
for (int i=0; i < 5; i++)
{
count++;
}
}
}


في الكود السابق ستلاحظ وجود متغير عام داخل الclass و هو متغير integer اسمه count و هو عبارة عن عداد و يمثل بالنسبة لنا Shared Resource أو متغير مشترك...
ستجد أيضاً انه تم تعريف Thread و تم اعلامها بالfunction الخاصة بها و هي عمل increment خمس مرات للمتغير count و في نفس الوقت تقوم الThread الرئيسية بعمل عملية مشابهة داخل الMain و هكذا أصبح لدينا 2 Threads يقوم كل منهم بعمل Increment خمس مرات بمجموع نهائي 10 حتى يصبح الناتج النهائي (نظرياً) 10 و لكن الواقع أن ال 2 Threads يتصارعان على نفس الShared Resource و هذه هي مشكلة الData Race ..
أين المشكلة...
المشكلة أنه يمكن ان يحدث Overlapping أو تداخل بين عمليات القراءة و الكتابة المتوازية فتخيل معي أن الThread الرئيسية قد قرأت القيمة في المتغير count ( و لتكن 0 ) و قامت بعمل increment و قبل أن تقوم بكتابة القيمة الجديدة في المتغير count كانت الThread الأخرى قرأت القيمة القديمة (القيمة 0) و بدأت العمل عليها ثم جاءت الthread الرئيسية و كتبت القيمة الجديدة (القيمة 1) و هنا كانت الThread الجديدة قد أنهت عملها و جاءت لتخزن القيمة الجديدة بعد الincrement و هي (القيمة 1) !!!!!!!!!!!
هكذا أصبح هناك خطأ لأنه نظرياً حدثت عمليتي increment للرقم صفر و لكن القيمة النهائية أصبحت 1 و هكذا لن تكون القيمة النهائية للبرنامج 10 كما هو متوقع !!!!!
مش مصدقني جرب الكود اللي فات .....
جربته؟؟؟
حتيجي تقوللي أنا جربت الكود و طلعت النتيجة النهائية 10 يعني كلامك مش صحيح...؟؟؟؟
لأ الكلام صحيح و لكن لكي تفهم ماحدث يجب أولاً أن نفهم كيف يُعامل نظام التشغيل الThreads ...
يقوم نظام التشغيل بمنح الThread وقتاً محدداً على الProcessor و يسمى Round و ليكن زمنها T و تقوم الthread بأداء عملياتها و هنا يحدث حدث من 3 احداث لتتوقف الThread عن استخدام الProcessor
الأول هو أن تنتهي الThread من عملها قبل انتهاء الزمن T و هنا يقوم نظام التشغيل بازالة الThread و وضع أخرى مكانها
الثاتي هو أن ينتهي الزمن T قبل أن تنتهي الThread من عملها و هنا يقوم النظام بعمل Block للThread و حفظ حالتها و وضع Thread أخرى مكانها الى أن يأتي دور الThread الأولى مرة أخرى فيقوم النظام بعمل UnBlock و اعادتها الى حالتها لتستكمل عملها
الحدث الثالث هو أن يقوم أي من النظام أو المستخدم بعمل Abort للThread و انهاء عملها.. أو عمل Sleep و هنا سيعاملها النظام كأنها قد أنهت الفترة المتاحة لها T و سيتم عمل Block لها الى ان يأتي دورها مرة أخرى

اذن ما الذي جعل الكود السابق -الذي نعتبره نظرياً خطأ - يعمل بشكل صحيح ؟؟؟
السبب هو أن الThread قصيرة جداً فهي تقوم بعملية بسيطة و هذا ماجعلها تستغرق وقتاً أقل من T فلم تحدث مشاكل الData Race و لكن يمكن أن تظهر المشكلة في جهاز اخر ابطأ أو تكون فيه T أقل و هذه تعتبر نقطة ضعف في الكود
يمكننا أن نرى هذه المشاكل باضافة بعض السطور المعطلة للكود هكذا
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->

CODE

using System;
using System.Threading;

public class Test
{
static int count=0;

static void Main()
{
ThreadStart job = new ThreadStart(ThreadJob);
Thread thread = new Thread(job);
thread.Start();

for (int i=0; i < 5; i++)
{
int tmp = count;
Console.WriteLine ("Read count={0}", tmp);
Thread.Sleep(50);
tmp++;
Console.WriteLine ("Incremented tmp to {0}", tmp);
Thread.Sleep(20);
count = tmp;
Console.WriteLine ("Written count={0}", tmp);
Thread.Sleep(30);
}

thread.Join();
Console.WriteLine ("Final count: {0}", count);
}

static void ThreadJob()
{
for (int i=0; i < 5; i++)
{
int tmp = count;
Console.WriteLine ("\t\t\t\tRead count={0}", tmp);
Thread.Sleep(20);
tmp++;
Console.WriteLine ("\t\t\t\tIncremented tmp to {0}", tmp);
Thread.Sleep(10);
count = tmp;
Console.WriteLine ("\t\t\t\tWritten count={0}", tmp);
Thread.Sleep(40);
}
}
}


ستجد أننا ملأنا الكود بتعليمات Thread.Sleep في أماكن مدروسة بين القراءة و التعديل و الكتابة حتى تظهر المشكلة و ستجد أن الناتج النهائي أصبح 6 و ليس 10 !!!!!!
اذن ماهو الحل لهذه المشكلة؟؟؟؟
الحل علمياً استخدام الMutual Exclusion أو مايطلق عليه اختصاراً الMutex و يطلق على الفكرة عموماً Monitors و تعتمد الفكرة على فكرة عزل المناطق التي يُحتمل حدوث هذه المشكلة فيها و هي مناطق التعامل مع الShared Resources و جعل هذه المناطق تعمل بطريقة غير متوازية ...
لكي تفهم فكرتها تخيل أننا وضعنا باباً له قفل على هذه المناطق بمجرد أن تدخل Thread من الباب تغلق الباب خلفها حتى تنتهي قم تخرج و تفتح القفل و أثناء وجودها في الداخل لا تستطيع اي Thread أخرى أن تدخل ....
و لاستخدام هذه الخاصية يمكننا استخدام الclass Monitor بوضع Monitor.Enter و Monitor.Exit حول هذه المناطق..
أو يمكننا استخدام تعليمة أخرى و هي lock هكذا
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->

CODE

using System;
using System.Threading;

public class Test
{
static int count = 0;
static readonly object countLock = new object();

static void Main()
{
ThreadStart job = new ThreadStart(ThreadJob);
Thread thread = new Thread(job);
thread.Start();

for (int i = 0; i < 5; i++)
{
lock(countLock)
{
int tmp = count;
Console.WriteLine("Read count={0}", tmp);
Thread.Sleep(50);
tmp++;
Console.WriteLine("Incremented tmp to {0}", tmp);
Thread.Sleep(20);
count = tmp;
Console.WriteLine("Written count={0}", tmp);
}
Thread.Sleep(30);
}

thread.Join();
Console.WriteLine("Final count: {0}", count);
}

static void ThreadJob()
{
for (int i = 0; i < 5; i++)
{
lock (countLock)
{
int tmp = count;
Console.WriteLine("\t\t\t\tRead count={0}", tmp);
Thread.Sleep(20);
tmp++;
Console.WriteLine("\t\t\t\tIncremented tmp to {0}", tmp);
Thread.Sleep(10);
count = tmp;
Console.WriteLine("\t\t\t\tWritten count={0}", tmp);
}
Thread.Sleep(40);
}
}
}


لاحظ أننا عرفنا متغير من نوع object و هو يمثل هنا القفل و استخدمنا نفس المتغير مع كل عملية lock تتعلق بنفس الshared resource و هكذا فانك عند تنفيذ الكود السابق فان النتيجة ستكون دائماً 10
و الى اللقاء قريباً في عرض لمشاكل الThreads مع الGUI
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->

مشاكل الThreading مع الGUI Controls و المشكلة الأساسية و هي في هذا المثال
لو فرضنا أننا نملك Form تحتوي على Label اسمه lblTest و سنقوم بتشغيل Thread لتغيير قيمة الText في هذا الLabel
هل هذه مشكلة؟؟؟؟
أعتقد أن الكثير منا سيشمر عن أكمامه و يكتب كود مشابه لهذا الكود
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->

CODE


public void InitThread()
{
Thread thread = new Thread(new ThreadStart(ChangeText));
thread.Start();
}

private void ChangeText()
{
this.lblTest.Text = "Changed";
}
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->


و لكن عند تنفيذ هذا الكود سنفاجأ بتوقف البرنامج عن العمل و ظهور Exception يوضح لنا أنه لا يُمكن للThread الجديدة أن تُعدل في أي Controls أنشأتها الParent Thread !!!!!!!!!!!!


والحل فى المره القادمه انشاء الله
}}}}
تم الشكر بواسطة:
#2
كاتب المشاركة : Marwan

الحل هو تعديل الخاصية المشتركة CheckForEllegalThreadCross للفئة Control الى False

أرجو أن لا أكون قد أفسدت الدرس
}}}}
تم الشكر بواسطة:


المواضيع المحتمل أن تكون متشابهة .
الموضوع : الكاتب الردود : المشاهدات : آخر رد
  درس-التشفير - الجزء الثانى(Deep in) RaggiTech 2 953 09-10-12, 06:44 PM
آخر رد: RaggiTech
  Windows Message Queue &amp; Message Pumping(Deep Threading 2) RaggiTech 0 272 08-10-12, 12:15 PM
آخر رد: RaggiTech
  (InvokeRequired &amp; BeginInvoke(Deep Threading 3 RaggiTech 0 339 08-10-12, 11:45 AM
آخر رد: RaggiTech

التنقل السريع :


يقوم بقرائة الموضوع: بالاضافة الى ( 1 ) ضيف كريم