1. مهمان گرامی، جهت ارسال پست، دانلود و سایر امکانات ویژه کاربران عضو، ثبت نام کنید.
    بستن اطلاعیه

WPF Multithreading

شروع موضوع توسط minaaa ‏29/1/11 در انجمن .Net

  1. کاربر پیشرفته

    تاریخ عضویت:
    ‏9/12/10
    ارسال ها:
    19,773
    تشکر شده:
    6,457
    امتیاز دستاورد:
    113
    مقدمه

    برخی اوقات ممکن است برنامه ها مجبور به انجام کارهایی باشند که نیاز به
    زمان طولانی دارند. در یک برنامه ساده پردازش کارها بصورت همزمان
    (synchronous) انجام می گیرد به این صورت که در هر لحظه فقط یک کار پردازش
    می شود و پس از اتمام آن پردازش کار بعدی آغاز شده و این کار تکرار می
    شود. تصور کنید که در شرایطی که UI منتظر اتمام پردازش یک کار طولانی است
    چه مشکلاتی ممکن است بوجود آید؟ معمولا UI‌ از کار می افتد و به حالت Not
    Respond‌ وارد می شود و تا زمانی که پردازش طولانی فعلی به پایان نرسد به
    همان صورت freeze باقی می ماند. به عنوان مثال زمانی که بخواهید بر روی
    گروهی از داده های موجود در یک database پردازشی انجام دهید، ممکن است با
    چنین مشکلی مواجه شوید. یک راه حل برای این مشکل استفاده از پردازش کارها
    بصورت غیر همزمان (asynchronous) می باشد که در اصطلاح Multithreading
    نامیده می شود.

    Single-Threaded Application Model

    اشیا در WPF‌ متعلق به thread ی میباشند که توسط آن ایجاد شده اند و
    thread های دیگر (از این به بعد این thread ها را با نام background
    thread هواهیم شناخت) قادر به دسترسی مستقیم به این اشیا نمی باشند. زمانی
    که یک شی به یک thread متصل می شود،‌ به آن شی single-threaded گفته می
    شود. بسیاری از اشیا در WPF اینگونه اند. این موضوع برای برنامه نویسان به
    این معنی است که شما نمی توانید اشیا UI را بصورت مستقیم و از thread‌ ی
    دیگر تغییر دهید. به عنوان مثال یک background thread نمی تواند بصورت
    مستقیم مقدار Text یک TextBox را که توسط thread اصلی برنامه ایجاد شده،
    تغییر دهد. البته WPF‌ دارای راه کارهایی برای انجام این کار می باشد.

    در تکنولوژی COM‌ این مدل از عمل threading با نام Single Threaded
    Apartment یا STA معرفی شده است که هم اکنون در فرم های ویندوزی نیز از
    همان مدل استفاده می شود. تفاوتی که پیاده سازی این مدل در WPF دارد،
    گسترش استفاده از آن و نیز به کارگیری DispatcherObject برای پیاده سازی
    است.

    اگر توجه کرده باشید می بینید که تابع main() به عنوان نقطه شروع برنامه
    هم در Windows App و هم در WPF App بصورت [STAThread] نشانه گذاری شده
    است.





    کد:
    [STAThread]

    static void Main()

    {

    {</pre>


    نشانه [STAThread] مشخص می کند که برنامه از مدل single-threaded
    apartment استفاده می کند و به عبارت دقیق تر، این کار وضعیت thread اصلی
    برنامه را به single-threaded تغییر می دهد.


    DispatcherObject و وابستگی thread

    اشیاء WPF ی که در مدل threading دخالت دارند هر کدام به نحوی از
    DispatcherObject‌ مشتق می شوند. مانند Application, Visual و
    DependancyObject ها، که همگی از کلاس های اساسی و پایه در معماری WPF
    محسوب می شوند. از این رو بسیاری از اشیا موجود در WPF در این مدل
    threading وجود دارند از جمله تمامی UI element ها. بنابراین هر شی که به
    نحوی از DispatcherObject مشتق شده باشد دارای وابستگی thread خواهد بود.



    DispatcherObject دو متد برای تشخیص اینکه شی مورد نظر بر روی thread مناسب قرار دارد یا خیر، ارائه می کند:

    VerifyAccess و CheckAccess. هر دو متد اساساٌ یک مقدار bool برمی گردانند
    که در صورت قرار داشتن شی بر روی thread مناسب مقدار true‌ خواهد داشت.
    متد VerifyAccess در صورت true نبودن یک InvalidOperationException ایجاد
    می کند به این معنی که شی مورد نظر بر روی thread مناسب قرار ندارد.





    کد:
    try

    {

    this.button1.VerifyAccess();

    MessageBox.Show(“The Button Has Access”);

    this.button1.Content = “I Belong”;

    }

    catch (InvalidOperationException e)

    {

    // Button does not have direct access, need to update through Dispatcher

    {</pre>


    برنامه ها به مکانسیمی نیاز دارند تا به ورودی های سیستم و کاربر پاسخ
    دهد. مانند کلیک ماوس، فشرده شدن کلیدهای صفحه کلید، event های سیستم و ...
    . در Win32، این مکانیسم message loop نام دارد و جزء جدایی ناپذیر همه
    برنامه های ویندوز می باشد. Message loop در background برنامه های ویندوز
    اجرا می شود و نقش آن هدایت message های سیستم به برنامه است. به عنوان
    مثال زمانی که کلید ماوس فشار داده می شود، ویندوز یک message به message
    loop می فرستد تا اعلام کند که کلید ماوس فشار داده شده است، همچنین مشخص
    می کند که کدام کلید فشار داده شده است، چپ یا راست. ویندوز این message‌
    را در صف اولویت (prioritized queue) برنامه ای که عمل کلیک توسط آن ایجاد
    شده، قرار می دهد. message loop‌ بصورت مداوم صف اولویت را پردازش کرده و
    message ها را به برنامه ارسال می کند. در WPF، message loop و صف اولویت
    توسط dispatcher ایجاد و مدیریت می شوند. یک dispatcher شیئی از کلاس
    Dispatcher است و در هر لحظه فقط یک عمل را بر حسب اولویتی که دارد، انجام
    می دهد.



    در ساده ترین حالت، یک برنامه فقط یک UI thread را اجرا می کند. البته یک
    یا چند worker thread دیگر نیز می توانند برای انجام کارها در background‌
    برنامه، ایجاد شوند. بر اساس مطالب ذکر شده،‌ اصولا یک worker thread‌
    برای تعامل با UI thread باید یک message به dispatcher مربوط به UI
    thread ارسال نماید که البته این امر امکان پذیر نخواهد بود زیرا تنها
    thread‌ ی که dispatcher‌ را ایجاد می کند به آن دسترسی دارد، به عبارت
    دیگر، یک رابطه یک به یک میان یک dispatcher و thread ی که آن را ایجاد
    کرده، وجود دارد.

    WPF یک dispatcher برای UI thread‌ ایجاد می کند بنابراین شما قادر به تعریف یک dispatcher‌ دیگر برای آن نخواهید بود.



    همانطور که قبلا نیز گفته شد، یک thread نمی تواند بصورت مستقیم به اشیایی
    که توسط thread دیگر ایجاد شده، دسترسی داشته باشد. Dispatcher با ارائه
    متدهای Invoke و BeginInvoke این امکان را به background thread یا worker
    thread می دهد تا با استفاده از delegate ها، به اشیا مورد نظر خود دسترسی
    داشته و آن ها را تغییر دهند.

    برای مشخص کردن اولویت message های ارسالی به dispatcher، از مقادیر موجود در DispatcherPriority enumeration استفاده خواهیم کرد:

    ApplicationIdle, Background, ContextIdle, DataBind, Inactive, Input, Invalid, Loaded, normal, Render, Send, SystemIdle



    یک مثال از WPF Multithreading :

    (به خطوط زرد توجه کنید)







    کد:
    using System.Threading;



    namespace BeRMOoDA_File_Encrypter

    {

    /// <summary>

    /// Interaction logic for Window1.xaml

    /// </summary>

    public partial class Window1 : Window

    {

    public Window1()

    {

    InitializeComponent();

    }

    delegate void UpdateUIDelegate(); //declare a delegate to run asynchronously

    Thread thread; //worker thread or background thread



    private void button1_Click(object sender, RoutedEventArgs e)

    {

    FolderBrowserDialog fb = new FolderBrowserDialog();

    if (fb.ShowDialog() == System.Windows.Forms.DialogResult.OK)

    {

    folderPath = fb.SelectedPath;

    password = textBox1.Text;

    //determine the method that background thread should run

    thread = new Thread(new ThreadStart(this.Encrypt));

    thread.Start(); //start background thread asynchronously

    }

    }





    void Encrypt()

    {

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

    GetFiles(folderPath, ref filesList);

    //declare delegate and set the changes we want made to UI

    UpdateUIDelegate ud = delegate() { progressBar1.Maximum = filesList.Count; };

    //send message to the UI’s dispatcher using Dispatcher class this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, ud);

    foreach (var file in filesList)

    {

    fCrypto.EncryptFile(file, password);

    UpdateUIDelegate ud2 = delegate() { progressBar1.Value++; };

    this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, ud2);

    }



    UpdateUIDelegate ud3 = delegate() { progressBar1.Value = 0; };

    this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, ud3);

    System.Windows.Forms.MessageBox.Show("Encryption finished.");

    thread.Abort();

    }</pre>