مطالب
معرفی و روش استفاده از Dispatcher در WPF
باید این سوال را از خودمان بپرسیم که اصلا چه نیازی به استفاده از Dispatcher در WPF می‌باشد و این که ما چه نیازی داریم با ساختمان Thread آشنا شویم؟

می‌دانید که در پروژه و نرم افزارهایی که توسعه داده می‌شوند بعضی مواقع مشاهده می‌کنیم قسمتی از برنامه نیاز به زمان یا پردازش بیشتری دارد تا عملیات خود را به اتمام برساند و رابط کاربری (User Interface) برنامه در این حین منتظر می‌ماند و یا به اصطلاح (Freeze) می‌شود تا یک پردازش طولانی به اتمام برسد و بعد رابط کاربری به کار خود ادامه می‌دهد و بعضی مواقع پنجره Windows explorer is not responding را مشاهده کرده‌اید که با کلیک بر روی Close the program از آن گذر می‌کنیم. در حالی که برنامه ما باید حالت Responsive  داشته باشد و نباید برنامه با یک‌چنین مواردی روبه رو شود.

ساختمان Thread :
تمامی اشیاء (Objects) در این مدل به دو گروه تقسیم بندی می‌شوند:
Single-Threaded Apartment
Multi-Threaded Apartment
ساختمان  Single-Threaded Apartment (STA) :

STA شامل فقط یک Thread می‌باشد و تمامی اشیاء در این ساختمان فقط می‌توانند متدی را که در این Thread صدا زده می‌شود دریافت کنند؛ یا به عبارتی دیگر هنگامیکه شیء به Thread ای متصل می‌شود دیگر شما قادر نخواهید بود اشیاء UI را به صورت مستقیم و یا از طریق Thread‌های دیگر تغییر دهید. لازم به ذکر است WPF از مدل STA پشتیبانی می‌کند که شامل نکات  زیر می‌باشد:
1. یک Thread در کل برنامه اجرا می‌شود و شامل همه Object‌های WPF می‌باشد.
2. عناصر یا المان‌های  WPF، قابلیت تنظیم Thread Affinity را دارند. منظور این می‌باشدکه Thread‌ها نمی‌توانند با یکدیگر ارتباط  برقرار کنند.
3. اشیاء WPF که قابلیت Thread Affinityرا دارند، از یک Object Dispatcher مشتق می‌شوند.
4. اشیاء WPF متعلق به Thread ای می‌باشند که توسط آن ایجاد شده است و Thread دیگر قادر به دسترسی مستقیم به این اشیاء نمی‌باشد.

ساختمان Multi-Threaded Apartment (MTA) :

MTA  شامل یک یا چندین Thread می‌باشد. تمامی اشیاء در این مدل می‌توانند از هر Thread ای فراخوانی شوند. در حقیقت رجیستر‌های CPU و Ram که در اختیار برنامه قرار داده شده تکه تکه می‌شوند و برنامه ما تعداد Thread‌های مورد نظر را اجرا می‌کند و یکی از راه حل‌های بر طرف کردن اینکه رابط کاربری ما در حالت انتظار باقی نماند استفاده از پردازش کارها بصورت غیر همزمان (Asynchronous) می‌باشد که در اصطلاح Multithreading نامیده می‌شود. 

WPF Dispatcher :

WPF از مدل STA پشتیبانی می‌کند. زمانیکه برنامه WPF  اجرا می‌شود، به طور خودکار یک Dispatcher Object ساخته می‌شود و متد Run صدا زده می‌شود و از آن برای آماده سازی صف پیام‌ها استفاده می‌شود که مدیریت یک صف از کارها بر عهده آن است و کارهای UI را در یک صف FIFO اجرا می‌کند. WPF یک Dispatcher را برای UI Thread ایجاد می‌کند. بنابراین شما نمی‌توانید یک dispatcher دیگر برای آن تعریف کنید. 

نکته : دیاگرام زیر نمایش می‌هد که تمامی اشیاء WPF از DispatcherObject مشتق شده‌اند.

نکته : زمانیکه برنامه WPF  اجرا می‌شود دو Thread ساخته می‌شود:
1.  UI Thread (Main Thread) 
2.  Render Thread
 

UI Thread : مسئولیت تمامی ورودی‌های کاربر، handle events, paints screen و اجرای کدهای برنامه را بر عهده دارد.

Render Thread : در Background اجرا می‌شود و برای Render صفحه نمایش WPF استفاده می‌شود.

نکته: همانطور که آشنا شدیم WPF نمی‌تواند UI Thread را از طریق یک Thread دیگر به روز رسانی کند و یا به عبارتی دیگر یک Thread نمی‌تواند بصورت مستقیم به اشیایی که توسط Thread دیگر ایجاد شده، دسترسی داشته باشد.

Dispatcher برای این کار دو متد را در اختیار ما قرار می‌دهد :

متد Invoke : یک Action یا Delegate را میگیرد و متد آن‌را به صورت همزمان اجرا می‌کند. این مورد به این معنا است که تا زمانی که اجرای متد کامل نگردد، عملیاتی صورت نخواهد گرفت و یا به عبارتی دیگر فراخوان را تا زمانیکه زمانبندی به پایان برسد، در حالت مسدود نگهداری خواهد کرد.

مثال :

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Task.Factory.StartNew(() =>
            {
                InvokeMethodExample();
            });
    }
 
    private void InvokeMethodExample()
    {
        Thread.Sleep(2000);
        Dispatcher.Invoke(() =>
            {
                btn1.Content = "By Invoke";
            });
    }
}
متد BeginInvoke : یک Delegate را می‌گیرد و متد آن‌را به صورت ناهمزمان اجرا می‌کند. این مورد به این معنا است که قبل از آنکه متد فراخوانی گردد، برگشت داده می‌شود و یا به عبارتی دیگر به ما اجازه می‌دهد تا Thread جاری را با کنترل دوباره به جریان بیندازیم.
مثال :
public MainWindow()
{
    InitializeComponent();
    Task.Factory.StartNew(() =>
        {
            BeginInvokeExample();
        });
}
 
private void BeginInvokeExample()
{
    DispatcherOperation op = Dispatcher.BeginInvoke((Action)(() => {
         btn1.Content = "By BeginInvoke";
        }));
}
BeginInvoke یک شیء DispatcherOperation برگشت می‌دهد. این شیء یا Object برای دانستن اینکه وضعیت عملیات کامل شده است یا خیر می‌تواند استفاده شود و همچین دو رویداد Aborted و Completed را فراهم می‌کند. 

مطالب
مروری بر کاربرد DoEvents

چند روز قبل هنگام استفاده از DoEvents در یک برنامه windows forms ، ناگهان پیغام stack overflow ظاهر شد! برای علت یابی و رفع آن کمی جستجو کردم که خلاصه‌ی آن به شرح زیر است:


DoEvents چیست؟

DoEvents یکی از متدهای کلاس Application در فضای نام System.Windows.Forms است.
ویندوز جهت مدیریت رخدادهای مختلف از یک صف استفاده می‌کند. رخدادهایی مانند کلیک ماوس، تغییر اندازه‌ی یک فرم و مواردی شبیه به آن ابتدا در یک صف قرار می‌گیرند و سپس پردازش می‌شوند. زمانیکه کنترلی مشغول پاسخ دهی به یک رخ‌داد می‌گردد، سایر رخ‌دادها هنوز در صف هستند و پردازش نخواهند شد. بنابراین اگر برنامه‌ی شما در یک روال رخ‌دادگردان کلیک، عملیاتی طولانی را در حال انجام باشد، بدلیل عدم پردازش سایر رخ‌دادها اینطور به نظر خواهد رسید که هنگ کرده است.
روش صحیح پردازش یک عملیات طولانی استفاده از یک ترد دیگر می‌باشد تا ترد اصلی برنامه که کار مدیریت رابط کاربر برنامه را به عهده دارد، درگیر این عملیات طولانی نشده و پاسخگوی رخ‌دادهای رسیده باشد.
راه میان‌بر و ساده‌ای که اینجا وجود دارد استفاده از DoEvents می‌باشد (بدون ایجاد یک ترد جدید). برای مثال اگر در روال رخ دادگردان کلیک یک برنامه، حلقه‌ای طولانی در حال پردازش است، هر از چندگاهی این متد فراخوانی شود، رخ‌دادهای در صف قرار گرفته فرصت ارسال به ترد اصلی برنامه را یافته و برنامه در حالت هنگ به نظر نخواهد رسید.
برای نمونه مثال زیر را در دو حالت با Application.DoEvents و بدون آن اجرا کنید:

private void btnProcessWithDoEvents_Click(object sender, EventArgs e)
{
for (int i = 0; i < 100000; i++)
{
TextBox1.Text = "Processing " + i.ToString();
Application.DoEvents();
}
}

در حالت بدون استفاده از Application.DoEvents ، تنها آخرین عبارت پردازش شده را در TextBox1 مشاهده خواهید کرد و همچنین در این حین، برنامه در حالت هنگ به نظر می‌رسد و برعکس.

مشکلات احتمالی حاصل از استفاده از Application.DoEvents :

الف) حس غلط پایان یافتن عملیات پیش از موعد
در مثال فوق در حین استفاده از Application.DoEvents ، دکمه‌ی btnProcessWithDoEvents مجددا فعال شده و قابل کلیک کردن می‌شود ولی آیا این بدین معنا است که پردازش قبلی به پایان رسیده است؟ به یک سری از کاربرها هم click-happy user گفته می‌شود! یعنی از کلیک کردن مجدد لذت می‌برند! در این حالت حتما باید دکمه‌ی btnProcessWithDoEvents را در ابتدای پردازش غیرفعال کرد و سپس در انتهای آن باید مجددا فعال شود.
مورد مشکل کلیک مجدد حتی می‌تواند منجر به تخریب اطلاعات در حال پردازش شود. فرض کنید برنامه در حال ذخیره‌ی اطلاعات در یک فایل است و کاربر مرتبا بر روی دکمه‌ی پردازش مربوطه کلیک کنید. فایل نهایی از یک سری اطلاعات ناهماهنگ و بی‌ربط پر خواهد شد.

ب) مشکل stack overflow
اگر علاقمند باشید، این مورد را می‌توان به صورت زیر شبیه سازی کرد:

یک تایمر را به برنامه اضافه کنید و یک دکمه. در روال رخ‌دادگردان کلیک مربوط به دکمه، دستورات زیر را اضافه کنید:

private void btnStartTimer_Click(object sender, EventArgs e)
{
this.timer1.Enabled = true;
this.timer1.Start();
this.timer1.Interval = 20;

}
و در روال tick مربوط به تایمر، دستورات زیر را اضافه کنید:

private void timer1_Tick(object sender, EventArgs e)
{
Thread.Sleep(50);
Application.DoEvents();
}
برنامه را اجرا کرده و یکی دو دقیقه صبر کنید، حتما با پیغام خطای stack overflow مواجه خواهید شد. چرا؟
فواصل زمانی اجرای تایمر به 20 میلی ثانیه تنظیم شده است اما در روال رخ‌داد گردان tick آن، نیاز به 50 میلی ثانیه (بیش از 20 میلی ثانیه) یا بیشتر برای اجرا دارد. با رسیدن به Application.DoEvents ، رخ‌داد در صف قرار گرفته‌ی دیگر tick بلافاصله اجرا می‌شود و همینطور الی آخر، تا بالاخره stack overflow حاصل خواهد شد.


پس چه باید کرد؟

الف) هنگام استفاده از Application.DoEvents به موارد فوق حتما دقت داشته باشید.
ب) بجای استفاده از این روش که در بیشتر موارد یک ضعف برنامه نویسی محسوب می‌شود، شروع به استفاده از روش‌های غیرهمزمان نمائید. برای مثال استفاده از :
BackgroundWorker
Asynchronous delegates
Threads

تنها موردی را که هنگام کار با تردها باید در نظر داشت این است که امکان دسترسی به کنترل‌های یک فرم را از ترد دیگری که آن کنترل را ایجاد نکرده است، ندارید و برای این مورد راه‌ حل‌های زیادی موجود است.
همچنین بخاطر داشته باشید در یک ترد استفاده از Application.DoEvents هیچ معنایی ندارد. ترد اصلی برنامه وظیفه‌ی به روز رسانی رابط کاربر برنامه و پاسخگویی به رخ‌دادهای رسیده را به عهده دارد. زمانیکه پردازش در تردی دیگر صورت می‌گیرد، ترد اصلی برنامه تا پایان پردازش متد شما قفل نخواهد شد که نیازی به استفاده از این متد باشد. در این حالت استفاده از Application.DoEvents ، سبب بالا رفتن مصرف حافظه‌ی برنامه و همچنین بالا رفتن میزان مصرف CPU خواهد شد.

جهت مطالعه بیشتر
Keeping your UI Responsive and the Dangers of Application.DoEvents


مطالب
لینک‌های هفته اول دی

وبلاگ‌ها و سایت‌های ایرانی

امنیت



ASP. Net


طراحی وب

PHP


اس‌کیوال سرور


سی شارپ


عمومی دات نت


مسایل اجتماعی و انسانی برنامه نویسی


کتاب‌های رایگان جدید


متفرقه
  • آهن بجای کروم! (یک برنامه نویس آلمانی قسمت‌هایی از مرورگر کروم را که در جهت جمع آوری اطلاعات برای گوگل بکار می‌رفته، حذف کرده و مرورگر دیگری به نام آهن را ارائه داده است!)

مطالب
لینک‌های هفته‌ی آخر بهمن

وبلاگ‌ها ، سایت‌ها و مقالات ایرانی (داخل و خارج از ایران)

امنیت

Visual Studio

ASP. Net

طراحی و توسعه وب

اس‌کیوال سرور

سی شارپ

عمومی دات نت

مسایل اجتماعی و انسانی برنامه نویسی

کتاب‌های رایگان جدید

متفرقه
اشتراک‌ها
AngularJs برای طراحان

مقاله ای در رابطه با AngularJs برای طرحان وب نه برنامه نویسان وب

AngularJs برای طراحان
نظرات مطالب
اعتبارسنجی سرویس های WCF
مرسی از جوابتون
امکان ست کردن تنظیمات سرور در وب کانفیگ هم هست؟ چون من سرویسمو در یک وب سایت گذاشتم.