الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

التعامل مع الThreads

مغلق
بدأه bashmohandes في 29 يناير 2006 · 21 رد · 4,574 مشاهدة · في Microsoft Visual C#.NET
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

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

مبروك علينا جميعاً عودة المنتدى بخير

ندخل في الموضوع

هل جربت من قبل أن تتعامل مع ال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 هو

using System.Threading

أهم Class في الThreading بالطبع هي System.Threading.Thread و هي التي توجد فيها أغلب العمليات الأساسية

اذاً هل الخطوة الثانية هي انشاء object من نوع Thread ؟؟

الحقيقة هذه الخطوة تسبقها خطوة قبلها و هي كتابة الfunction التي ستقوم هذه الThread بتنفيذها و لتكن مثلاً

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

ثم نقوم بعمل الObject من نوع Thread

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

ثانية واحدة ايه اللي انت كاتبه ده ؟؟؟؟ مين ThreadStart دي و ازاي كاتب اسم الfunction كأنه parameter ؟؟؟؟

لو سألت نفسك السؤال السابق اذن فأنت أول مرة تتعامل مع الDelegates و هي باختصار شديد بديل محترم لما كنا نستخدمه في ال++C/C و هو الFunction Pointer ...

الحقيقة المجال لا يتسع لشرح الDelegate هنا و لكن يمكنك أن ترجع للMSDN أو ابحث في المنتدى... و لو مستعجل كل اللي محتاج تعرفه في السطر اللي فات اني بقول للThread بتاعتي ان الfunction اللي حتنفذها اسمها PrintHello

شفت خرجتني من الموضوع ازاي؟؟؟ :angry: على العموم ما علينا

بقيت خطوة أخرى و هي تشغيل الThread نفسها باضافة سطر اخر

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 لتنفيذ هذه العملية و يكون هكذا

ThreadPool.QueueUserWorkItem(new WaitCallback(PrintHello));

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

هل الحياة وردية دائماً في الThreading ؟؟؟؟

بالطبع لا و توجد مشاكل عديدة و سيكون مقالي التالي عن المشاكل الشهيرة و الشائعة و طرق تفاديها ....

سلام ;)

Sr. Software Development Engineer
Hulu, LLC
My Blogs

#2

شكرا جدا علي الموضوع يا بشمهندس

نطلب منك المزيد فالموضوع مهم جدا وشيق

يا ريت مع توضيح في اي المواقف استخدم ايها نوع من ال threads

ارجو ان يكون في القريب العاجل

اذا لم تزد شيئا علي الحياة فلا تكن انت زائدا عليها

#3

جزاك الله خيرا

فأرجوا أن تكمل الباقي لأننا ننتظرة ، وبالأخص كيف أعرف أن العملية الأخرى Thread مازال يعمل أم انه انتهى من أداء مهمتة ؟

وللمعلومة للأخوة الأعزاء كان آخر مشروع لي أمس في موضوع "مشاريع صغيره لتعلم الـ #C, أكواد تعليمية للمبتدئين"

وبالأخص المشروع التاسع والعشرون يتكلم عن ما تفضل به الأخ الكريم حسام

تم تعديل هذه المشاركة بواسطة Ahmed H. Alawady في 29 يناير 2006 في 14:31

Name : Ahmed H. Alawady

Web Site : Alawady.info

Email : alawady_ahmed@hotmail.com

Tel : +2 012 345 6808

#4

thanx man for ur active articles

#5

جزاك الله خير على الموضوع المهم جدا

لدي بعض التساؤلات التي أتعبتني

كيف أوقف الThread في منتصف عمله?

جربت Abort ولكنها (تقذف) ب ThreadAbortedException

كيف يمكنني ارسال باراميترات للThread بحيث يكون كالتالي:

Thread thread = new Thread(new ThreadStart(SendEmailMessage(Msg, Email, Subject )));

لدي مشكلة عند انهاء البرنامج وهي أنه يبقى في الذاكرة مع أني استخدمت Application.Exit

ولك جزيل الشكر و التقدير

#6

كل هذه التساؤلات و أكثر ان شاء الله سأجيب عليها الليلة أو غداً على أقصى تقدير

Sr. Software Development Engineer
Hulu, LLC
My Blogs

#7
ابوأنس كتب:
جزاك الله خير على الموضوع المهم جدا

لدي بعض التساؤلات التي أتعبتني

كيف أوقف الThread  في منتصف عمله?

جربت Abort ولكنها (تقذف) ب ThreadAbortedException

كيف يمكنني ارسال باراميترات للThread بحيث يكون كالتالي:

Thread thread = new Thread(new ThreadStart(SendEmailMessage(Msg, Email, Subject )));

لدي مشكلة عند انهاء البرنامج وهي أنه يبقى في الذاكرة مع أني استخدمت Application.Exit

ولك جزيل الشكر و التقدير

اذا أردت انهاء عمل الThread بصورة غير طبيعية فليس أمامك سوى Abort و هذه الطريقة تتسبب في ظهور ال ThreadAbortedException و هذا للتفريق بين اذا كانت الThread انتهت نهاية طبيعية أم أنها تم انهاءها عنوة...

بالنسبة لارسال Parameters للThreadStart فهذه الDelegate معرفة على أنها لا تأخذ أي معاملات و تقوم بعمل Return لأي نواتج أيضاً

و لهذا يجب عليك التحايل قليلأً لعمل هذا تابع هذا المثال

public class UrlFetcher
{
    string url

    public UrlFetcher (string url)
    {
        this.url = url;
    }

    public void Fetch()
    {
        // use url here
    }
}

[... in a different class ...]
              
UrlFetcher fetcher = new UrlFetcher (myUrl);
new Thread (new ThreadStart (fetcher.Fetch)).Start();

في هذا المثال نريد أن نقوم بانشاء Threadلعمل عملية معينة على URL و نريد أن نُعطي الThread اسم الURL الذي نريده

في هذه الحالة سنقوم بعمل Class تحوي الParameters الخاصة بالعملية و نقوم بعمل Constructor لعمل initialization لهذه المتغيرات و أيضاً نقوم بعمل Function تتبع الDelegate ThreadStart

و هكذا يمكننـا استخدام الfunction عن طريق وضع الURL في الObject ثم استخدام الfunction داخل الThread

و تم حل هذه المشكلة في Dot Net 2.0 حيث أصبحت الThread تستخدم نوعاً اخر من الdelegates بالاضافة الى ThreadStart و هو ParameterizedThreadStart و هو يسمح بfunction تأخذ متغير واحد من نوع object و يتم ادخال قيمة المتغير أثناء استدعاء Start هكذا

[

In some method or other]
Thread t = new Thread (new ParameterizedThreadStart(FetchUrl));
t.Start (myUrl);

[And the actual method...]
static void FetchUrl(object url)
{
    // use url here, probably casting it to a known type before use
}

بالنسبة لموضوع انهاء البرنامج أعتقد أنه يجب عليك أن تقوم بعمل Join لجميع الThreads التي قمت بانشائها حتى يستطيع البرنامج أن يخرج بسلام...

أو يمكنك استخدام Environment.Exit و لكن هذه الfunction تقتل البرنامج

Sr. Software Development Engineer
Hulu, LLC
My Blogs

#8

استكمالاً للدرس السابق سنتكلم اليوم عن مشكلة كبيرة جداً تواجه البرامج التي تعمل فيها أكثر من Thread و هي مشكلة ال Data Race أو التسابق...

شرح هذه المشكلة بسيط

انظر معي الى هذا الكود

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 أقل و هذه تعتبر نقطة ضعف في الكود

يمكننا أن نرى هذه المشاكل باضافة بعض السطور المعطلة للكود هكذا

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 هكذا

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

Sr. Software Development Engineer
Hulu, LLC
My Blogs

#10

فائدة Thread.join :

اقتباس
Blocks the calling thread until a thread terminates.

أي تعمل block لل Thread حتى يتم ازالته من ال memory

متى يتم استخدامه؟

اقتباس
Use this method to ensure a thread has terminated. The caller will block indefinitely if the thread does not terminate.

This method changes the state of the calling thread to include ThreadState.WaitSleepJoin. You cannot invoke Join on a thread that is in the ThreadState.Unstarted state.

#11

السلام عليكم

شكرا أخي bashmohandes على الدرس الرائع

بالنسبة لسؤال الأخ ابوأنس عند اغلاق البرنامج باستخدام Application.Exit

لا يتم انهاء ال Thread و لانهاء الThread يجب عليك كتابة الكود التالي داخل الحدث

Form Closing كالتالي

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
  {
 	 System.Environment.Exit(System.Environment.ExitCode);
  }
#12

هل تريد إيقاف خيط أو Thread ما؟؟ خذ المنطق :

public class MySpecialPrinter{
int i=0;
bool run=true;
public void print(){
while(run){
Console.Write(i++);
}
}
public void stop(){
run =false;
}
}

public class Test{
public static void Main(){
MySpecialPrinter p=new MySpecialPrinter();
ThreadStart job = new ThreadStart(p.print);
       Thread thread = new Thread(job);
thread.start(); //This thread will never stop
// in another part of your code:
p.stop();
}

}

ربما تضع p.stop() في حدث زر مثلا! بالتوفيق!

#14

استخدام أمر Abort خطر نسبيا،، و(قد) يُعرّض برنامجك إلى الخطر! وقد بين المهندس محمد حسام ذلك في شرحه عندا ذكر مشكلة تداخل الخيوط. مشكلة أمر Abort أنه قد يوقف الخيط وهو يحاول تغيير قيمة مهمة.. أو مثلا وظيفة الخيط أن يغير قيمتين بطريقة خاصة جدا،، ربما أمر Abort يوقف الخيط عندما ينتهي من تغيير المتغير الأول وقبل أن يغير المتغير الثاني وهذا سيء جدا وقد يضر برنامجك.

لاحظ أمر Stop الذي أنشأته فهو لن يضر البرنامج ولا تعدد المتغيرات المتعلقة ببعضها إن وجدت،، ولاحظ أيضا أنه لا يرمي خطأ!

تستفيد من هذه الطريقة إذا كانت بيانات برنامجك مهمة جدا، والخطأ فيها لا يغتفر!

بالتوفيق!

#16
أبومازن كتب:
استخدام أمر Abort خطر نسبيا،، و(قد) يُعرّض برنامجك إلى الخطر! وقد بين المهندس محمد حسام ذلك في شرحه عندا ذكر مشكلة تداخل الخيوط. مشكلة أمر Abort أنه قد يوقف الخيط وهو يحاول تغيير قيمة مهمة.. أو مثلا وظيفة الخيط أن يغير قيمتين بطريقة خاصة جدا،، ربما أمر Abort يوقف الخيط عندما ينتهي من تغيير المتغير الأول وقبل أن يغير المتغير الثاني وهذا سيء جدا وقد يضر برنامجك.

Abort is dangerous if called by external thread. But if the the thread calls abort for itself, then it is safe.

Abort is important because it triggers the garbage collection to remove unused objects created by this thread.If you don't call abort while you are no more needing the thread, then you are wasting memory that can be used by other running threads.

أبومازن كتب:
لاحظ أمر Stop الذي أنشأته فهو لن يضر البرنامج ولا تعدد المتغيرات المتعلقة ببعضها إن وجدت،، ولاحظ أيضا أنه لا يرمي خطأ!

I guess you mean ThreadAborException. ThreadAbortException does not indicate an error. It is the ordinary way in which .NET uses to terminate threads. ThreadAbortException is special case for the CLR, which means that if you try to catch it in a catch block, it will be rethrown automatically at the end of the catch block. A lot of ordinary .NET methods throw this exception, such as Server.Transfer() method in ASP.NET

#17

نكمل اليوم موضوعنا في ال Threading و سنتكلم عن مشاكل الThreading مع الGUI Controls و المشكلة الأساسية و هي في هذا المثال

لو فرضنا أننا نملك Form تحتوي على Label اسمه lblTest و سنقوم بتشغيل Thread لتغيير قيمة الText في هذا الLabel

هل هذه مشكلة؟؟؟؟

أعتقد أن الكثير منا سيشمر عن أكمامه و يكتب كود مشابه لهذا الكود

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

private void ChangeText()
{
this.lblTest.Text = "Changed";
}

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

هل هذا يعني أنه لا يمكن للChild Thread أن تغير في أي Control على الForm ؟

بالطبع لا, يمكن للthread أن تقوم بتغيير الControls و لكن قبل أن نشرح الطريقة يجب أولاً أن نُعطي نبذة صغيرة عن

Windows Message Queue & Message Pumping

ببساطة

عندما تقوم بانشاء و تشغيل أي مشروع من أي نوع فان هناك ما يُسمى الMain Thread و هي الthread التي تقوم بتنفيذ ال Main function و هي التي تتولى تنفيذ العمليات الموجودة في البرنامج ...

و لكن

طبيعة البرامج ذات الواجهات الرسومية GUI Application مثل Windows Forms تتميز بأن المستخدم يستطيع أن يتفاعل مع الForm أو البرنامج بشكل عام و في نفس الوقت يقوم البرنامج بتنفيذ هذه الأوامر بالاضافة الى الحفاظ على شكل الForm أي أنه يقوم برسمها مرة بعد أخرى ...

هنا يقوم الWindows بتحويل جميع العمليات و الأوامر على الForm الى ما يُسمى Messages و يضعها بترتيب حدوثها في الMessage Queue حتى يتم تنفيذها و يتم تنفيذ هذه العملية عن طريق الMessage Pumping بأن يقوم البرنامج بعد التشغيل بتشغيل Module يقوم بعمل loop على جميع الMessages داخل الMessage Queue و ينفذ الأوامر المتعلقة بكل Message مثل الضغط على زر أو تحريك المؤشر و هكذا يمكننا تخيل عملية الMessage Pumping كأنها على هذا الشكل

Message message = null;
while ((message = GetNextMessage()) != null)
{
ProcessMessage(message);
}

و هكذا يستمر البرنامج في العمل حتى تنتهي كل الMessages أو تأتي Message لانهاء البرنامج نفسه ...

و هنا نسأل أين تُنفذ عملية ال Message Pumping ؟؟؟؟

الجواب بسيط في الMain Thread و الدليل أنه اذا تسببت احد الMessages التي يستغرق تنفيذها بعض الوقت الى تجمد الشاشة و هذا لأن الMain Thread مشغولة بتنفيذ الMessage و لا يوجد لديها وقت لاعادة رسم الForm ....

سؤال اخر متى يتم تشغيل الMessage Pumping ؟؟؟

يتم تشغيل الMessage Pumping مع تنفيذ هذه الinstruction الموجودة في كل Windows App و هي

Application.Run(new AnyFormYouWant());

داخل الMain

و الان بعد أن أخذنا نبذة عن الMessage Queueing دعونا نغوص أكثر داخل الWindows Messaging in Win32

ماذا كنا نفعل أيام الWin32 API's لتغيير خاصية في Form أو أي شئ من هذا القبيل؟؟

كنا نملك 2 API's و هما

SendMessage

PostMessage

و كلٍ منهما له استخدام أمثل و هنا يجب أن نعرف الفرق بينهما أولاً

SendMessage

تقوم بوضع Message جديدة داخل الMessageQueue و تنتظر الى أن تقوم عملية الMessage Pumping بتنفيذ هذه الMessage حتى تنتهي [blocking Send] و هي الطريقة الأنسب لتغيير الخواص من ال Main Thread و لكنها ستفشل عند استخدامها من Thread أخرى لتغيير خواص Control أنشأه الMain Thread

PostMessage

تقوم أيضاً بوضع Message جديدة داخل الMessage Queue و لكنها لا تنتظر حتى ينتهي تنفيذ الMessage أو حتى يبدأ التنفيذ فهي تنتهي بمجرد وصول الMessage الى الQueue و هذا يُسمى [Non Blocking Send] و هي تصلح في حالة الChild Thread تريد تغيير خواص في Control أنشأه الMain Thread لأنها أشبه بترك رسالة من الChild Thread الى الMain Thread حتى تقوم الMain Thread أثناء عملية الPumping بتنفيذها.. اي في النهاية الMain Thread هي التي قامت بالتغيير

و الان نعود مرة أخرى الى الDot Net

ماذا يحدث عندما يتم تنفيذ عملية مثل

myForm.Height = 100;

يقوم الJIT Compiler بتحويل هذا الأمر الى SendMessage و يتم عندها ارسال Message لتغيير طول الForm و توضع في الQueue حتى يتم تنفيذها في الMessage Pumping و هذه العملية تنجح فقط اذا كانت الMain Thread و التي أنشأت الForm منذ البداية هي التي قامت بعمل تغيير الخاصية...

و لكن ماذا اذا أردت أن استخدم Post Message ؟؟؟؟ هل يمكنني ذلك؟

بالطبع يمكنك ذلك عن طريق استخدام BeginInvoke أو Invoke الموجودتين في كل الControls و في هذه الحالة يتم ارسال Message الى الQueue و لا ننتظر انتهاء تنفيذها فوراً... و هذا هو ما نحتاجة حتى تستطيع الChild Threads تغيير خصائص الForm أو الControls

و الآن نعود الى نفس الCode الذي كتبناه في أول المقال و الذي أردنا به تغيير Label Text باستخدام Thread أخرى...

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

private void ChangeText()
{
if(this.InvokeRequired)
{
this.BeginInvoke(new ChangeDelegate(ChangeText), null);
return;
}
this.lblTest.Text = "Changed";
}

و الان بعض التعديلات ظهرت على الكود و في هذه المرة الكود سيعمل طبيعياً و سأشرح ماتم تعديله

قبل الشرح هناك نقطة صغيرة...

كيف أستطيع أن أحدد متى سأستخدم BeginInvoke و متى لن أستخدمها ؟؟

الجواب بسيط توجد داخل كل Control خاصية Property تدعى InvokeRequired و هي من نوع bool و تكون قيمتها ب False اذا كان الMain Thread هو الذي بقوم بتنفيذ InvokeRequired و تكون بtrue عندما تكون أي Thread أخرى ...

اذاً يمكننا الان فهم الكود السابق ببساطة فكل الفكرة فيه تكمن في الif condition التي تستشف من يقوم بتنفيذ هذا الكود فإذا كان الMain Thread فسيتم تغيير الText باستخدام الText Property مباشرة أما اذا كانت thread اخرى فسيتم ارسال رسالة بواسطة PostMessage من خلال BeginInvoke ووضعها في الMessageQueue و هنا عندما يأتي دور الMain Thread في التنفيذ فانها ستقوم بتغيير الText بنفسها

اعرف أن هذا الدرس دسم و لكنه مهم جداً و أرجو أن أجد تفاعل ايجابي مع الموضوع حتى نصل لأكبر استفادة ان شاء الله...

و شكراً

Sr. Software Development Engineer
Hulu, LLC
My Blogs

#18

الموضوع جميل جداومفيدا جدا جدا

وده بعد ال training الذي تفضل به علينا الباشمهندس في mdc

يعني شوفت بعيني محدش قالي

لاتحسبن المجد تمرا انت اكلهوا.... لن تبلغ المجد حتى تلعق الصبرا

ومن يتهيب صعود الجبال ...:::... يعش أبد الدهر بين الحفر

"وَيَا قَوْمِ اسْتَغْفِرُوا رَبَّكُمْ ثُمَّ تُوبُوا إِلَيْهِ يُرْسِلِ السَّمَاءَ عَلَيْكُمْ مِدْرَارًا وَيَزِدْكُمْ قُوَّةً إِلَى قُوَّتِكُمْ وَلَا تَتَوَلَّوْا". (أيه 52 سورة هود)

أَمَرَ بِالِاسْتِغْفَارِ الَّذِي فِيهِ تَكْفِير الذُّنُوب السَّالِفَة وَبِالتَّوْبَةِ عَمَّا يَسْتَقْبِلُونَ وَمَنْ اِتَّصَفَ بِهَذِهِ الصِّفَة يَسَّرَ اللَّه عَلَيْهِ رِزْقه وَسَهَّلَ عَلَيْهِ أَمْره وَحَفِظَ شَأْنه وَلِهَذَا قَالَ " يُرْسِل السَّمَاء عَلَيْكُمْ مِدْرَارًا "

وَفِي الْحَدِيث" مَنْ لَزِمَ الِاسْتِغْفَار جَعَلَ اللَّه لَهُ مِنْ كُلّ هَمّ فَرَجًا وَمِنْ كُلّ ضِيق مَخْرَجًا وَرَزَقَهُ مِنْ حَيْثُ لَا يَحْتَسِب ".

(تفسير ابن كثير)

#19

السلام عليكم

جزاك الله اخي بش مهندس خيرا على هذه المعلومات الرائعة

ان قلت ال thread يقوم بتقسيم البرنامج الى عمليات بحيث تعالج بشكل متوازي parallelly ولكن سؤالي هو

نحن لانستخدم سوى معالج واحد فكيف سيقوم بمعالجتها بشكل متوازي اليس من المفترض ان تعالج على شكلconcurrency

اي ان المعالج سوف يقوم بتنفيذ جزء من عملية 1 على سبيل المثال ثم 2 ثم 3 وينتقل من جديد الى1 والى 2 وهكذا

الى ان ينتهي من البرنامج

ارجوان يكون سؤالي واضحا وارجوالاجابة وجزاك الله خيرا

#20
Xhack كتب:
ان قلت ال thread  يقوم بتقسيم البرنامج الى عمليات بحيث تعالج بشكل متوازي parallelly ولكن سؤالي هو

نحن لانستخدم سوى معالج واحد فكيف سيقوم بمعالجتها بشكل متوازي اليس من المفترض ان تعالج على شكلconcurrency

اي ان المعالج سوف يقوم بتنفيذ جزء من  عملية 1 على سبيل المثال  ثم 2 ثم 3 وينتقل من جديد الى1 والى 2 وهكذا

الى ان ينتهي من البرنامج

What you say is true. multithreading have two main uses:

1-Increasing performance, and this is only if you have multiple processors, where you divide the processing task among the processors by using multiple threads.

2-Increase the responsiblilty of your applications using asynchronous function calls. if you call a function that will take some time to execute (like getting some data from a web service), you don't want the GUI of your application to stop responding to the user until the function executes. this is done through multithreading.

#21

حاولت تنفيذ الخطوات على VB ولكن لم تظهر عندي كلمة ChangeDelegate

وايضا اذا اردت التغيير على موقع نفس النموذج ككل بما فيه

كأن اغير موقعه بالاضافة الى قيمة مربع نص داخله؟؟

هل هناك طريقة لكي اخبر الthread بأن التغيير سيتم على كل النموذج بالاضافة الى عدد من العناصر بداخلها؟

<span style='color:green'>سؤل ابن عباس عن علمه كيف جمعه فقال :( أوتيت لسانا سؤولا وقلبا عقولا)</span>

#22

أنا جديد جداً على threading وأريد أن أعرف كيف أتعامل معه في هذا المثال..

هذا الكود أقوم باستدعائه من timer بحيث يقوم بالتشغيل كل 5 ثوان وربما أقل أو أكثر حسب المستخد ولكن المشكلة أنه في كل مرة تتجمد عندي الفورم حتى ينتهي من الكود ومن ثم تتجمد مرة أخرى عندما يقوم باستدعائه ثانية..

ما الحل لو سمحتم...

هل يمكن الشرح خطوة بخطوة..

للعلم أنا أقوم بعمل ping للعناوين الموجودة في ListView وأريد أن أرى النتيجة فورية لا أن تتجمد الفورم ولا أرى ما يحصل إلا عندما تنتهي العملية..

مع الشكر الجزيل

public void ping_to_host()
{
	for (listview_item = 0; listview_item <= lstHosts.Items.Count - 1; listview_item++)
	{
		string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
		byte[] buffer = Encoding.ASCII.GetBytes(data);

		Ping ping_sender = new Ping();
		PingOptions ping_options = new PingOptions();
		ping_options.DontFragment = true;

		PingReply ping_reply = ping_sender.Send(lstHosts.Items[listview_item].SubItems[1].Text, 5000, buffer, ping_options);

		if (ping_reply.Status == IPStatus.Success)
		{
			lstHosts.Items[listview_item].SubItems[0].Text = "Success";
			lstHosts.Items[listview_item].SubItems[2].Text = ping_reply.RoundtripTime.ToString();
			lstHosts.Items[listview_item].SubItems[3].Text = ping_reply.Options.Ttl.ToString();
			lstHosts.Items[listview_item].BackColor = Color.Black;
		}
		else
		{
			switch (ping_reply.Status)
			{
				case IPStatus.BadDestination: ping_status = "Bad Destination"; break;
				case IPStatus.BadHeader: ping_status = "Bad Header"; break;
				case IPStatus.BadOption: ping_status = "Bad Option"; break;
				case IPStatus.BadRoute: ping_status = "Bad Route"; break;
				case IPStatus.DestinationHostUnreachable: ping_status = "Destination Host Unreachable"; break;
				case IPStatus.DestinationNetworkUnreachable: ping_status = "Destination Network Unreachable"; break;
				case IPStatus.DestinationPortUnreachable: ping_status = "Destination Port Unreachable"; break;
				case IPStatus.DestinationProhibited: ping_status = "Destination Prohibited"; break;
				// case IPStatus.DestinationProtocolUnreachable: ping_status = "Destination Protocol Unreachable"; break;
				case IPStatus.DestinationScopeMismatch: ping_status = "Destination Scope Mismatch"; break;
				case IPStatus.DestinationUnreachable: ping_status = "Destination Unreachable"; break;
				case IPStatus.HardwareError: ping_status = "Hardware Error"; break;
				case IPStatus.IcmpError: ping_status = "ICMP Error"; break;
				case IPStatus.NoResources: ping_status = "No Resources"; break;
				case IPStatus.PacketTooBig: ping_status = "Packet Too Big"; break;
				case IPStatus.ParameterProblem: ping_status = "Parameter Problem"; break;
				case IPStatus.SourceQuench: ping_status = "Source Quench"; break;
				case IPStatus.TimedOut: ping_status = "Timed Out"; break;
				case IPStatus.TimeExceeded: ping_status = "Time Exceeded"; break;
				case IPStatus.TtlExpired: ping_status = "Ttl Expired"; break;
				case IPStatus.TtlReassemblyTimeExceeded: ping_status = "Ttl Reassembly Time Exceeded"; break;
				case IPStatus.Unknown: ping_status = "Unknown"; break;
				case IPStatus.UnrecognizedNextHeader: ping_status = "Unrecognized Next Header"; break;
				default: ping_status = "Unknow"; break;
			}

			lstHosts.Items[listview_item].SubItems[0].Text = ping_status;
			lstHosts.Items[listview_item].SubItems[2].Text = "";
			lstHosts.Items[listview_item].SubItems[3].Text = "";
			lstHosts.Items[listview_item].BackColor = Color.Red;
		}

		toolStatus.Text = "Idle - " + DateTime.Now.ToLongTimeString();
		// ping_thread.Abort();
}

هذا الموضوع مغلق.

مواضيع مشابهة