מחפש חומר בסיסי על Threading בNet.

מחפש חומר בסיסי על Threading בNet.

כרגע אני בונה אפליקציית WinForms (שקשורה לרשת) שמטבע הדברים חייבת להיות מרובת נימים (כדי לחסוך את הזמן של הResponse). קודם יצא לי לעבוד עם Threading רק בסביבת consule ובnet 1.1. וכשניסיתי ליישם את הטכניקות הישנות לאפליקציית WinForm ב net 2.0 נתקלתי בבעיה שלא ניתן (בדרכים המוכרות לי) להשפיע מתוך thread שונה על הForm. מישהו מכיר חומר בסיסי-מתקדם על הנושא? (אנגלית - רוסית - עברית) ובעיקר על איך אמורה להראות הארכיטקטורה של המחלקה שתומכת בריבוי נימים ובWinForm? תודה.
 

Justin Angel

New member
קה פרובלמה מואי צ'יקיטה?

יש לי מאמר מתבשל שיהיה מוכן מתישהו החודש בנובמבר על 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(); }​
 
תודה! חצי מהמאמר אני רואה כבר פה!!

האמת היא שבאפליקציה שלי הבעיה היא שאני רוצה לראות תיעוד צמוד של מה שקורה בthreadים בGUI... כלומר תוך כדי ריצת הפרוצדורה שאני רוצה לארוז כthread נפרד היא יכולה "לירות" מספר אירועים, ועבודתה של הפרוצדורה בניגוד לפיבונאצ'י האינסופי לוקחת 25 שניות גג (אם זה מתערך יותר, אני פשוט "הורג" את הthread). לכן אני מתעניין דווקה בשיתוף פעולה בין threadים לבין הGUI. כרגע אני עובד על הוצאה של כל האירועים מתוך המתודה שאני מריץ כthread נפרד לחוצה לו ומשנה את המתודה כך שתקרא להם דרך ממשק delegate ע"י Client.Invoke.... האם זו הדרך היחידה? יש משהו יותר נח? עוד מטלה עומדת פני היא שאני רוצה שבכל רגע נתון (עד סיום רשימת המטלות) ירוצו עד 5 threadים, כלומר עם אחד הסתיים מייד נוצר thread חדש שמקבל את המטלה הבאה התור) ורצוי שגם זה יעשה בלי לתקוע את הGUI (אולי דרך BackgroundWorker) תודה על התגובה המפורטת ועל המאמר שבדרך.
 

Justin Angel

New member
../images/Emo26.gif

בנושא הצגת עבודה ב-Threadים שנפתחו, ל-BackgroundWorker יש אירוע בשם ProgressChanged (או משהו כזה) שמאפשר לך להישאר "בקשר צמוד עם ה-Threadים האחרים" כפי ששאלת. בדוגמה המלאה ב-MSDN יש גם דוגמה לאירוע הזה ושימוש ב-ProgressBar ב-GUI. כמו שהסברתי את כל המתודות הרלוונטיות תרשום ל-BackgroundWorker.DoWork ותריץ אותו. אם הן מבצעות עבודה דומה (כלומר יש סיבה לצפות לתוצאה בעלת מבנה זהה) בכל ה-Threadים ניתן ליצור BackgroundWorker אחד או אם מדובר במטלות בעלות אופי שונה ניתן ליצור לכל אחת מהן BackgroundWorker משלו. בתוך אירוע הסיום של ביצוע BackgroundWorker תוכל לשנות את ה-BackgroundWorker הקיים או לפתוח BackgroundWorker חדש שיריץ איזה משימות נוספות שאתה רוצה להריץ.
 

Zeliran

New member
או:

Invoke
 

Justin Angel

New member
../images/Emo26.gif

ואיך ה-Delegate משנה את ה-GUI? כרגע סיכמנו שזה בלתי אפשרי ולכן זה לא פתרון רלוונטי.
 

Zeliran

New member
?

אם אני נמצא בת'רד של ה MMI ויוצר מתוכו ת'רד אחר, אז אם אני אשתמש ב invoke בת'רד החדש.. זה יעבוד לא רע (להזכירך, היה קיים דבר כזה גם ב 1.1)
 

Justin Angel

New member
../images/Emo26.gif

נכון, Invoke יעבוד, אבל הוא סינכרוני. ככה שאם מבצעים עבודה חישובית או כל עבודה שלוקחת זמן ב-Invoke היא עדיין תתקע את ה-GUI וסיכמנו שמנסים להימנע מזה, ככה שזה לא כזה קשור לשיחה. חשבתי שאתה מדבר על BeginInvoke היות ודיברנו על Threadים.
 

Zeliran

New member
////

הפעולה שלוקחת הרבה זמן מתבצעת על ת'רד שונה ולכן לא תוקעת את ה GUI
 

Justin Angel

New member
../images/Emo26.gif

זה בדיוק אותו Thread. לפי מה שאתה אומר הקוד הבא לא יגרום למסך לקפוא:
public delegate void HaveNoReturnValueAndNoParametersEventHandler(Thread ); private void button1_Click(object sender, EventArgs e) { HaveNoReturnValueAndNoParametersEventHandler del = new HaveNoReturnValueAndNoParametersEventHandler(ChangeLabel); del.Invoke(); } private void ChangeLabel() { Thread.Sleep(20000); label1.Text = "Boom"; }​
לעומת זאת, ה-GUI כן קופא. ואם באמת היה מדובר ב-Threaf נפרד, הגישה ל-Label הייתה מפילה את הריצה. בוא נעשה בדיקה קצת יותר חד-משמעית מאשר להפיל את ה-GUI. נשלח למתודה ב-Invoke את ה-Thread הראשי ושתבדוק אם הוא שווה ל-Thread שלה ותכניס ל-Label.
public delegate void GetStringEventHandler(Thread myThread); private void button1_Click(object sender, EventArgs e) { GetStringEventHandler del = new GetStringEventHandler(ChangeLabel); del.Invoke(Thread.CurrentThread); } private void ChangeLabel(Thread myThread) { label1.Text = Thread.CurrentThread.Equals(myThread).ToString(); }​
התוצאה היא True ולכן רואים שאכן שני ה-Threadים הם בדיוק אותו Thread.
 

Zeliran

New member
תשים לב

שהבעיה היתה להשפיע על ה MMI מתוך thread שונה, ולזאת הכוונה
 

Justin Angel

New member
../images/Emo26.gif

אבל זה לא Thread שונה! אז אתה שוב אם אותה בעיה, אין פה שום Mutli-Threading, שום ביזור, שום דבר, רק GUI שנתקע. Invoke לא פותח Thread חדש. הוא עובד על ה-Thread הקיים.
 
Nil ref error

לדעתי הכוונה היא להריץ את החישוב ב thread אחד ולעבור ל thread של ה UI ע"י שימוש ב mainForm.Invoke(del);
 
הכוונה שהלוגיקה לא מתבצעת בInvoke

אלא רק עדכון הGUI שלא לוקח זמן משמעותי. כבל מקרה, אני יעיין בתיעוד של BackgroundWorker כנראה שאחרי הכל הוא כן מתאים לצרכיי. תודה רבה.
 

ytrumper

New member
ביצוע שתי פעולות background בו זמני

אשמח לקבל תשובה על השאלה הבאה: נאמר שבעליה של form אני רוצה לטעון נתונים מה db. בו זמנית ארצה לתת למשתמש לטייל במסך. אשתמש ב backgroundworker לביצוע הטעינה והמשתמש יהיה חופשי לעבוד. כעת בזמן שהטעינה מתבצעת המשתמש בחר לבצע פעולה נוספת מול השרת שגם היא אורכת זמן מה. ארצה לבצע גם את הפעולה הזו ברקע. האם עלי להשתמש באותו ה bgw? או שעלי ליצור bgw חדש לכל פעולת רקע? במידה ואני מנסה להשתמש באותו ה bgw (אל אך שאני מצליח להבדיל בין הפעולות באמצעותה ה arguments) במידה והפעולה הראשונה לא הסתיימה, אני מקבל exception על כך שה bgw נמצא כעת בפעולה. מה הפתרון? תודה, יוני
 
עוד שאלה בנושא...

כעת אני לגמרי מבולבל... אחרי שיישמתי ב"ה בהצלחה ריבוי נימים במחלקה שלי, נוצר בעיה (אמנם צפויה) אחרת: thread safety במערכים (במקרה שלי ArrayList). כאן נתקלתי בבלגן איסופי שאני מקווה שג'סטין יעשה בו סדר. זה בערך מה שמצאתי על הנושא: SyncLock Collections and Synchronization (Thread Safety) Managed Threading Best Practices כעת רק כמה שאלות: א. אם ההגדרה של SyncLock היא שלא ניתן להריץ בו זמנית שני נימים של הקוד המוקף, אז מה זה עוזר במקרה של מערכים? הרי אם למשל אסרתי על המתודה של מחיקת פריט לרוץ בו זמנית, עדיין באותו הזמן יהיה ניתן להוסיף פריט... ב. וכן אם משתמשים בפונקציה ArrayList.Synchronized זה מחזיר מופע של SyncArrayList שבסך הכל מקיף כל מתודה של הArrayList המקורי בSyncLock אז חזרנו לבעיה "א"... ג. אם התשובה לשני השאלות הקודמות היא Monitor (אני מאוד מקווה שלא), אז אני לא מצליח למצוא ברשת איזו דוגמה מובנת וקריאה. אני מדגיש שוב, המטרה שלי היא שבזמן שthread אחד עובד עם המערך לא יהיה ניתן לגשת משער הthreadים לאף אחת מהמתודות או מהproperty של אותו המערך. (והthreadים יחכו בתור) תודה מראש.
 
למעלה