نمایش نتایج 1 تا 40 از 144

نام تاپیک: منابع، مراجع و مقالات مفید تالار

Threaded View

پست قبلی پست قبلی   پست بعدی پست بعدی
  1. #8
    کاربر تازه وارد
    تاریخ عضویت
    فروردین 1386
    محل زندگی
    کرمانشاه
    پست
    51

    بکارگیری همزمانی پیچیده با استفاده از کلاس Monitor

    استفاده از کلاس Monitor همیشه با احتیاط همراه بوده . چرا که استفاده نابجا و نادرست از متدهای این کلاس در برنامه نویسی به شیوه MultiThreading همیشه با دردسر و باگ های گاها کشف نشدنی همراه بوده و برنامه نویسان مکررا با متدهای پیچیده این کلاس (Wait,Pulse,PulseAll) سختی های زیادی را بجان می خرند.
    واقعیت اینست که متدهای Wait,Pulse,PulseAll بصورت ذاتی پیچیده هستند چراکه قدرت زیادی را در همزمانی به ارمغان آورده اند . از آنجا که کارکردن با این متدها به آشنایی خوبی در رابطه با MultiThreading احتیاج دارد ، پس توصیه من اینست که ابتدا در زمینه MultiThreading به تسلط خوبی برسید ، سپس متدهای Wait,Pulse,PulseAll کلاس Monitor را بکار بگیرید تا قدرت واقعی آنها را مشاهده کنید ، در غیر اینصورت این مفاهیم اصلا به شما روی خوش نشان نخواهند داد..
    خوب ابتدا تعریف هایی از این متدها ارائه میشود ، سپس سراغ یک مثال میرویم
    متد Wait : این متد قفلی را که در اختیار Thread جاری قرار دارد را آزاد میکند و Thread جاری به حالت انتظار میرود تا وقتیکه از سوی Thread دیگری سیگنالی دریافت کند و کار خودرا از سر بگیرد. خصوصیت جالب این متد اینست که درصورتیکه برای چندین مرتبه Thread جاری شیء همزمانی را قفل کند ، زمانی که این متد فراخوانی میشود و پس از بدست آوردن دوباره فقل(زمانیکه Thread از حالت انتظار خارج میشود)به همان دفعات که شیء همزمانی ، قبل از فراخوانی قفل شده بود ، شیء همزمانی را قفل میکند..
    متد Pulse : این متد به Threadی که در حالت انتظار به سر میبرد سیگنالی میفرستد که آنرا از صف انتظار خارج میکند و Thread ، به صف آماده ، برای تصاحب قفل میرود. البته الزاما Threadی که سیگنال را دریافت کرده است دوباره قفل را در اختیار نمیگیرد.باید توجه داشت اعمال سیگنال در صورتیکه هیچ Threadی در حالت انتظار نیست باعث میشود که فقط سیگنال از بین برود و هیچگونه عملیات دیگری برای از دست ندادن سیگنال انجام نمیشود. این نکته را هم باید در نظر گرفت که اعمال Pulse برای شی ای که قفل شده است انجام میگیرد ، نه اینکه قفل برروی یک شیء و اعمال Pulse برای شیئ دیگری بکار برود.
    متد PulseAll : عملکرد این متد هم مانند متد Pulse میباشد با این فرق که تمامی Thread های صف انتظار تاثیر می پذیرند (نه فقط یک Thread ) ولی تنها یک یا چند Thread به صف آماده میروند ، تا یکی از آنها برای تصاحب قفل انتخاب شود.
    خوب تا اینجا همه چیز تئوری بود و پر ازاستثناء و نکات فراوان. سراغ مثال میرویم تا همه چیز ان شاء الله جا بیافتد .
    مثالی که میخواهیم با هم مرور کنیم اینست که قصد داریم سرعترین و بی وقفه ترین عملیاتی که ممکن است یک کد انجام دهد را با یک فرم ویندوزی همزمان کنیم. در این مثال از دو قفل همزمانی و دو Thread بهره میگیریم یعنی:
    1-Thread اول که مستقیما ایجاد نمی شود ، بلکه از طریق فراخوانی غیر همزمان یک Delegate برای تولید Threadی که عمل اصلی را انجام میدهد استفاده میشود.چونکه عمیاتی که این Thread انجام میدهد خیلی سریع میباشد از فراخوانی غیر همزمان استفاده کرده ایم در غیر اینصورت هنوز میتوانستیم تولید Thread را خودمان انجام بدهیم. وظیفه این Thread نوشتن مقدار متغیر حلقه در فیلد counter میباشد. برای انجام این کار باید از قفل گذاری استفاده کنیم.
    2-Thread دوم که عمل خواندن مقدار را از فیلدی به نام counter بر عهده دارد و برای این کار باید از قفل گذاری استفاده کنیم.
    کد مثال مورد نظر به ترتیب زیر آمده است :
    1)	        private delegate void DoWork(int a);
    2) private int counter;
    3) private Thread second;
    4) private object syncObj = new object();
    5) private object printLock = new object();


    خط اول تعریف Delegateی را ارائه می کند که برای چاپ مقدار صحیحی روی یک لیبل روی فرم بکار می رود .
    خط دوم تعریف فیلد counter آمده است.
    خط سوم : تعریف Thread دوم که وظیفه همزمانی با متدهای doOperation و changeForm را برعهده دارد .
    خط چهارم برای تعریف قفل همزمانی syncObj آمده که برای ایجاد همزمانی مابین متدهای doOperation (Thread اول) و operationThread2 (Thread دوم) لازم است .
    خط پنجم هم برای همزمانی مابین متدهای operationThread2 و changeForm تعریف شده است .

    1)	        public Form1()
    2) {
    3) InitializeComponent();
    4) }

    تعریف سازنده فرم.

    1)	        private void button1_Click(object sender, EventArgs e)
    2) {
    3) Action d = this.doOperation;
    4) second = new Thread(this.operationThread2);
    5) second.Start();
    6) d.BeginInvoke(null, null);
    7) }


    نقطه شروع منطق اصلی برنامه. ابتدا باید دکمه ای با نام Button1 را تعریف کنید ، سپس در رویداد کلیک این کدها نوشته میشوند. (لیبلی با نام label1 هم مورد نیاز است)
    خط سوم با استفاده از Delegateی به نام Action که در فضای نام System تعریف شده است ، اقدام به کپسوله سازی متد doOperation میکنیم.
    خط چهارم : مقداردهی Thread دوم (second) که متد operationThread2 را اجرا خواهد کرد
    خط پنجم : شروع متد operationThread2 که توسط Thread دوم انجام میشود.
    خط ششم : فراخوانی غیر همزمان متد .doOperationبا این فراخوانی چرخه همزمانی کامل میشود و همه چیز از الان به بعد شروع میشود.
    1)	        private void doOperation()
    2) {
    3) for (int i = 0; i <= 4000; i++)
    4) {
    5) Thread.Sleep(TimeSpan.FromMilliseconds(.5));
    6) lock (this.syncObj)
    7) {
    8) this.counter = i;
    9) Monitor.Pulse(this.syncObj);
    10) }
    11) }
    12) lock (this.syncObj)
    13) {
    14) Monitor.Pulse(this.syncObj);
    15) }
    16) }
    17)
    18) private void operationThread2()
    19) {
    20) int a = 0;
    21) DoWork d = new DoWork(this.changeForm);
    22) while (a < 4000)
    23) {
    24) lock (this.syncObj)
    25) {
    26) Monitor.Wait(this.syncObj);
    27) a = this.counter;
    28) }
    29) lock (this.printLock)
    30) {
    31) if (this.InvokeRequired)
    32) {
    33) this.BeginInvoke(d, a);
    34) }
    35) else
    36) {
    37) this.changeForm(a);
    38) }
    39) }
    40) }
    41) }
    42)

    متد doOperation (ترد اول)
    این متد سریعترین عملیات ممکن را انجام میدهد(فقط اضافه کردن مقدار به شمارنده حلقه).
    خط سوم : شمارنده ای تعریف میکنیم که از 0 تا 4000 را میشمارد.
    خط پنجم : برای جلوگیری از اتمام سریع حلقه به یک مقدار تاخیر احتیاج داریم.
    خط ششم : شئ همزمانی syncObj را برای ایجاد همزمانی مابین این متد و متد operationThread2 قفل میکنیم. این قفل گذاری به این دلیل انجام میشود که میخواهیم با اعمال این تکنیک مطمئن شویم که متد operationThread2 از تغییرات حاصله در این متد آگاهی پیدا میکند .در خط هشتم و در حالیکه مطمئن هستیم که قفل در این Thread قراردارد میتوانیم مقدار فیلد counter را با شمارنده حلقه مساوی قرار دهیم.
    خط نهم : متد doOperation در حالی به کار خود ادامه میدهد که در متد operationThread2 ودر حلقه While مطمئن هستیم که کنترل اجرا یا در خط 28 قرار دارد که منتظر دریافت سیگنال است و یا در خارج از بلاک قفل syncObj میباشد. اگر حالت اول برقرار باشد با استفاده از متد پالس و فرستادن سیگنال متد operationThread2 میتواند به کار خود ادامه دهد اما اگر حالت دوم را داشته باشیم در این صورت سیگنالی را که فرستاده ایم به هدر میرود.
    خطوط 12 تا 15 : اگر در آخرین فراخوانی متد پالس(خط نهم) حالت دوم رخ بدهد در اینصورت Thread دوم (operationThread2) هرگز پایان نمی یابد چراکه آخرین پالسی که برای رهایی Thread دوم از حالت انتظار فرستاده شده است گم شده و حالا در حالت انتظار ابدی بسر میبرد. برای حل این مشکل درست پس از اتمام حلقه دوباره قفل را از Thread دوم می گیریم (که قفل را رها کرده) سپس پالسی را جهت رهایی از حالت انتظار ارسال میکنیم سیگنال ارسال شده باعث میشود حلقه While در خط 22 پس از انجام عملیاتهای بلاک قفل گذاری بعدی به اجرای خود پایان دهد . در صورت عدم تعبیه کدهای خطوط 12 تا 15 لیبل روی فرم تا مقدار 3999 پیش میرود و هیچگاه پایان نمی یابد و Thread دوم به یک Thread خائن تبدیل میشود . اگر دکمه زمانیکه برای اولین بار فشار داده شود در حین شمارش در صورتیکه (لیبل مرتبا در حال تغییر است) دوباره دکمه را کلیک کنیم چندین و چند Thread به صورت همزمان لیبل را تغییر میدهند پس از اتمام همه Threadها وجود خطوط 12 تا 15 باعث میشوند آخرین Thread به یک Thread خائن تبدیل نشود و با زدن دکمه Close فرم ، برنامه بصورت عادی پایان می پذیرد . توجه داشته باشید که وجود خطوط 12 تا 15 برای حالتی که تنها یک Thread به متد operationThread2 دسترسی دارد بدون تاثیر است!!!!!!!!!!! (دکمه فقط یکبار کلیلک شده)
    متد operationThread2 (ترد دوم)
    در خط 20 متغیر a را برای نگهداری مقادیر فیلد تعریف میکنیم
    خط 21 : برای جلوگیری از تولید استثناء و در جهت چاپ مقدار فیلدcounter روی لیبل بایستی از Delegate ها کمک گرفت . چونکه میخواهیم هیچگونه تاخیری نداشته باشیم و چندین به متد دسترسی داشته باشند مجبوریم ازیک Delegate با امضای DoWork که در قسمت تعاریف کلاس فرم آمده است استفاده کنیم. کد متدی که با اینDelegate کپسوله سازی میشود متد changeForm است.
    خط 22 : متغیر صحیحی که قبلا تعریف کرده بودیم (a)را شرط حلقه قرار میدهیم تا در صورت رسیدن فیلد به 4000 از حلقه خارج شویم.
    خط 24 سعی میکند قفل را برای همزمانی با متد doOperationتصاحب کند پس از تصاحب قفل در حالیکه قفل را در اختیار داریم ، میخواهیم کاری کنیم که ابتدا متد doOperationدر Thread اول قفل را در اختیار بگیرد ، فیلدcounter را تغییر بدهد سپس به این متد سیگنالی را بفرستد تا بتوان از مقدار فیلدcounter استفاده کرد. توجه کنید که اگر جای خطوط 26 و 27 عوض میشد آنوقت واقعا ممکن بود قبل از اینکه کنترل اجرا در متد doOperation باعث شود که Thread دوم در متد operationThread2 از تغییر فیلد counter آگاهی پیدا کند ، حلقه خاتمه پیدا کند.(قبل از گرفتن قفل از سوی doOperation و درست بعد از ورود به حلقه خط 22 ، فیلدcounter توسط Thread دیگری برابر 4000 بشود!!!!!!!).
    خط 27 : پس از رهایی از انتظار در خط 26 میتوانیم مقدار فیلدcounter را در متغیر ذخیره کنیم.
    خط 29 : حالا که مقدار به روز شده فیلد counterرا در متغیر محلی a ذخیره کرده ایم میتوانیم مقدار آنرا برای چاپ روی لیبل فرم بفرستیم . چون سرعت اعمال درخواستهای مکرر از سوی متد operationThread2 (ترد دوم)
    بالا میباشد و برای جلوگیری از روی هم قرار نگرفتن این درخواستها از قفل دیگری بنامprintLock استفاده میکنیم تا تعادل مناسبی را بین درخواستها و عملیات قابل مشاهده بودن تغییر متن لیبل بوجود آورده باشیم ، به اینصورت که تا آخرین عملیات تغییر متن لیبل به پایان نرسد تقاضای جدیدی برای تغییر متن لیبل با استفاده از قفل printLock پذیرفته نمیشود(از سوی هر Thread دیگری ).
    خطوط 31 تا 38 : پس از تصاحب قفل در این خطوط فراخوانی متد changeForm کپسوله شده را با مقدار متغیر به صورت غیر همزمان انجام میدهیم.

    1)	      private void changeForm(int a)
    1) {
    2) lock (this.printLock)
    3) {
    4) this.label1.Text = a.ToString();
    5) this.label1.Refresh();
    6) }
    7) {
    8)

    خط دوم قفل را تصاحب میکند و اجازه نمیدهد فراخوانی های مکرری انجام شود. و تا وقتیکه عمل رفرش (تغییر قابل احساس روی لیبل) انجام نشود قفل را رها نمیکند.
    نکاتی که در رابطه با این نمونه برنامه قابل تامل هستند عبارتند از:
    1) برای کار با متدهای قدرتمند کلاس Monitor بایستی آماده دریافت هرگونه نتیجه دور از عقل باشید.
    2) همیشه قبل از اینکه شروع به اعمال همزمانی کنید باید تمامی Threadها را در نقطه دلخواه مجبور به پیروی از قانون خود کنید ، در صورتیکه این قسمت را فراموش کنید ، براحتی سردرگم خواهید شد
    3) باید بدانید که در چه صورت همزمانی میبایست پایان یابد و شرایط را برای Threadهای پایان ناپذیر با استفاده از قفل گذاری مناسب از بین ببرید.
    4) کار ، همیشه اینقدر سریع انجام نمیشود . چون سعی داشتم بیشترین پیچیدگی را اعمال کنم از عملیات سریعی مانند حلقه استفاده کرده ام . اما شاید هیچ وقت احتیاج پیدا نکنید که با این شرایط و نکات فراوان سروکله بزنید ، معمولا کارهایی مانند خواندن فایل ، نوشتن در پایگاه داده ها و...... آنقدر زمانگیر هستند که احتیاج ندارید مثلا از
    Thread.Sleep(TimeSpan.FromMilliseconds(.5));
    استفاده کنید
    5) سعی کنید از آنچه که در زمان اجرا و همزمانی رخ میدهد سر در بیاورید . چرا که واقعا نکات جالبی را می آموزید .
    6) در نهایت باید گفت استفاده از کلاس Monitor دریچه جدیدی در رابطه با MultiThreading به سوی برنامه نویس باز میکند ، چرا که با بکار گیری این کلاس با استفاده از کد کمی به پیچیدگی و قدرت بالایی دست پیدا میکنید
    موفق باشید
    فایل های ضمیمه فایل های ضمیمه
    آخرین ویرایش به وسیله Sajjad1364 : چهارشنبه 18 شهریور 1388 در 21:33 عصر

برچسب های این تاپیک

قوانین ایجاد تاپیک در تالار

  • شما نمی توانید تاپیک جدید ایجاد کنید
  • شما نمی توانید به تاپیک ها پاسخ دهید
  • شما نمی توانید ضمیمه ارسال کنید
  • شما نمی توانید پاسخ هایتان را ویرایش کنید
  •