PDA

View Full Version : مقاله: WPF Multithreading



Amir Oveisi
جمعه 18 مرداد 1387, 02:08 صبح
::: pdf اش پایین موجوده :::

مقدمه
برخی اوقات ممکن است برنامه ها مجبور به انجام کارهایی باشند که نیاز به زمان طولانی دارند. در یک برنامه ساده پردازش کارها بصورت همزمان (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()
{
{
نشانه [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
{

WPF Dispatcher
برنامه ها به مکانسیمی نیاز دارند تا به ورودی های سیستم و کاربر پاسخ دهد. مانند کلیک ماوس، فشرده شدن کلیدهای صفحه کلید، 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.Threadi ng.DispatcherPriority.Normal, ud);
foreach (var file in filesList)
{
fCrypto.EncryptFile(file, password);
UpdateUIDelegate ud2 = delegate() { progressBar1.Value++; };
this.Dispatcher.BeginInvoke(System.Windows.Threadi ng.DispatcherPriority.Normal, ud2);
}

UpdateUIDelegate ud3 = delegate() { progressBar1.Value = 0; };
this.Dispatcher.BeginInvoke(System.Windows.Threadi ng.DispatcherPriority.Normal, ud3);
System.Windows.Forms.MessageBox.Show("Encryption finished.");
thread.Abort();
}






منابع:
Wrox.Professional.WPF.Programming.May.2007
OReilly.Programming.WPF.2nd.Edition.Aug.2007

eli_joon
سه شنبه 26 شهریور 1387, 13:32 عصر
dariuosh بیا اینجا
http://barnamenevis.org/forum/showthread.php?t=122847