Question from .Net Tapuz forum: Multi-Threading in Winforms applications
שאלה:
כרגע אני בונה אפליקציית WinForms (שקשורה לרשת) שמטבע הדברים חייבת לעבוד בצורה Multi-threaded (כדי לחסוך את הזמן של הResponse).
קודם יצא לי לעבוד עם Threading רק בסביבת console ובnet 1.1. כואשר ניסיתי ליישם את הטכניקות הישנות לאפליקציית WinForm ב net 2.0 נתקלתי בבעיה שלא ניתן (בדרכים המוכרות לי) להשפיע מתוך thread שונה על הForm. כיצד בכל זאת ניתן לעבוד עם Threadים באפליקציות חלונאיות?
תשובה:
יש לי מאמר מתבשל שיהיה מוכן מתישהו החודש בנובמבר על Multi-threading. כהרגלי, מאמר של ג'סטין.
לעבודה א-סינכרונית ב-Winforms מומלץ להשתמש ב-BackgroundWorker.
הקונספט הוא כזה, בכל עבודה א-סינכרונית באפליקציות חלונאיות תמיד יהיו שתי שלבים: תעשה משהו מאוד מאוד ארוך, ואז תשנה משהו קטן וזניח בטופס בעקבותו. הבעיה היא שכפי ששמת לב, לא ניתן לגשת מ-Thread שאינו ה-Thread הראשי לרכיבים הגרפיים של ה-GUI.
למשל אם אני ארצה לחשב את המספר ה-2 מיליון אחרי הנקודה של פאי ולהציג את הספרה הזו בפקד Label.
אם נעבוד בלי Multi-threading, למשל נכתוב את כל הקוד בלחיצה על כפתור ב-GUI, מה שנקבל זה שבזמן שהחישוב המאוד ארוך ומורכב הזה מתבצע הטופס הגרפי שלי יקפא ולא יגיב יותר למשתמש. לא ניתן יהיה להזיז אותו, הוא לא יצטייר מחדש יותר ושום דבר בעצם.
אז נחליט להוציא את כל העיבוד הזה ל-Thread נוסף ונגיד שגם נשלח ל-Thread הזה הפנייה ל-Label ככה שיוכל לשנות את הטקסט בסוף העיבוד הזה. ה-Thread הזה מקבל הקצאת זמן-מעבד שונה מה-Thread הראשי של האפליקציה שלנו ולכן האפליקציה שלנו כרגע רצה לכאורה על שני מעבדים מקביליים. מעבד אחד מריץ את ה-GUI ומעבד אחד מבצע עיבוד. ברגע שה-Thread השני יסיים, הוא יגש ל-Label לנסות לשנות את הטקסט לתוצאה המיוחלת ואז נקבל שגיאת זמן ריצה ששום Thread מלבד ה-Thread הראשי אינו רשאי לגרום לאירוע שמצייר מחדש את ה-GUI.
אז כאן אנחנו נכנסים לדילמה. ברור לנו שצריך לעבוד עם Threadים וברור לנו ש-Threadים לא עובדים איתנו.
במקרה של "נעבוד הרבה הרבה ואז נציג תוצאה" קיים רכיב מיוחד לאפליקציות חלונאיות שתומך בתהליך הזה.
הרכיב הוא BackgroundWorker.
את החישובים המורכבים (או כ-כלל העבודה שלוקחת הרבה זמן עד כדי כך שיש להפרידה ל-Thread נפרד) תשים במתודה כחלק מאירוע ה-DoWork של ה-BackgroundWorker. את התוצאה תכניס לתוך DoWorkEventArgs.Result בתוך המתודה שעושה DoWork.
בסיום תקבל את התוצאה במתודה שנרשמה לאירוע RunWorkerCompleted ושם תשנה את ה-GUI בהתאם.
למשל הדוגמה מ-MSDN:
BackgroundWorker Class
// in form consturctor
backgroundWorker1.DoWork +=
new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(
backgroundWorker1_RunWorkerCompleted);
// This event handler is where the actual,
// potentially time-consuming work is done.
private void backgroundWorker1_DoWork(object sender,
DoWorkEventArgs e)
{
// Get the BackgroundWorker that raised this event.
BackgroundWorker worker = sender as BackgroundWorker;
// Assign the result of the computation
// to the Result property of the DoWorkEventArgs
// object. This is will be available to the
// RunWorkerCompleted eventhandler.
e.Result = ComputeFibonacci((int)e.Argument, worker, e);
}
// This event handler deals with the results of the
// background operation.
private void backgroundWorker1_RunWorkerCompleted(
object sender, RunWorkerCompletedEventArgs e)
{
resultLabel.Text = e.Result.ToString();
}
קישור: http://www.tapuz.co.il/tapuzforum/main/Viewmsg.asp?forum=831&msgid=88542803