
STAThread, או לקבוע את המצב לטרד בודד בעזרת SetApartmentState. מומלץ לא להעביר את כל האפליקציה ל-STA, אלא רק טרד שקורא לפונקציה שדורשת זאת.DoWork רץ בטרד נפרד ואסור בו לגעת ב-UI. ProgressChanged רץ בטרד ה-UI ומעודכן בעזרת ReportProgress. RunWorkerCompleted רץ בטרד ה-UI ומקבל את התוצאה ב-e.Result. הדגלים WorkerReportsProgress ו-WorkerSupportsCancellation מאפשרים דיווח והפסקה. הביטול נעשה בעזרת CancelAsync ובדיקת CancellationPending בתוך הלולאה.הטקסט המלא של הדקות הראשונות בהרצאה «Threads - חלק 8» בקורס הווידאו של PracticU – כתוביות בעברית עם זמן התחלה לכל פסקה. כך תוכלו לקרוא ולחפש את הנושאים לפני שמתחילים ללמוד.
הדבר הבא שכדאי להכיר בעולם הטרדים זה שני מונחים שנקראים STA ו-MTA. בואו נסתכל מה בעצם הם אומרים. יש לנו פה תוכנית חדשה, Console Application, STA ו-MTA.
אז המונח STA אומר Single Thread Apartment ו-MTA אומר Multi Thread Apartment. מה שזה אומר זה בעצם שיטה שמערכת ההפעלה עובדת עם טרדים.
למערכת ההפעלה יש שתי אפשרויות לעבוד עם טרדים. MTA זה בעצם השיטה שבה מערכת ההפעלה פותחת טרד נפרד לפעולה מסוימת. STA זה אומר שמערכת ההפעלה פותחת טרד ונותנת לטרד הזה כל מיני פעולות לפי הסדר.
.NET עובדת by default כ-MTA אבל יכול להיות מצב שאנחנו ב-Dotnet מפעילים איזה משהו שמבחינת מערכת ההפעלה הוא עובד בשיטת STA ולכן אנחנו מ-Dotnet חייבים לעבור לשיטת ה-STA ויש לנו שתי אפשרויות לבצע את המעבר הזה מ-MTA ל-STA אפשרות אחת זה על ידי אטריביוט sta-thread מעל המיין וזה אומר שמעכשיו
אנחנו בשיטת sta או הדיפולט זה mta-thread וזה אומר שאנחנו בשיטת ה-mta אנחנו כמובן יכולים לעשות את זה מעל פונקציה מסוימת. אפשרות אחרת אם בתוך הקוד בשלב מסוים אנחנו רוצים לבצע את המעבר הזה אז אנחנו יכולים להשתמש ב-thread נקודה זאת בעצם פונקציה שהיא לא או סטטית אז בואו נפתח פה איזשהו אובייקט טרד,
נניח שיש לנו פה איזושהי פונקציה, אז יש לנו את הפונקציה set apartment state והיא מקבלת enum שאומר שאנחנו mta או sta בצורה כזאת ואז ספציפית את הטרד הזה העברנו למצב שבו הוא עובד כשיטת ה-sta של windows ואז הוא יכול לדוגמה בתוך הפונקציה שהוא מפעיל להפעיל פונקציות שעובדות בשיטת ה-STA מבחינת Windows.
אז כאן אם בעצם אנחנו נתקלים במצב שבו אנחנו רוצים להשתמש עם איזושהי פונקציית מערכת של Windows ופתאום אנחנו חוטפים איזשהו exception וה-exception מדבר על STA או MTA או Single Thread Apartment או Multi Thread Apartment בדרך כלל זה יקרה כאשר פונקציה של Windows הכוונה הפונקציה של ה-API של
Windows עובדת רק בשיטת STA. אנחנו בדוטנט נמצאים בשיטת MTA, כלומר טרדים רגילים, ואז אנחנו חייבים לעשות סוויץ' ולעבור לשיטת STA.
אם זה המצב ואתם חוטפים את האקספצ'ן הזה, אתם יודעים עכשיו איך לבצע את המעבר הזה. לא מומלץ להעביר את כל האפליקציה לשיטת STA כמובן, אלא מומלץ לפתוח איזשהו טרד, להגדיר שהוא יהיה בשיטת ה-STA, ואז שהטרד הזה יקרא לפונקציה של Windows.
אני לא רוצה להיכנס כרגע לדוגמאות, כי אז בעצם אני נכנס לנושא שאנחנו נגיע אליו בהמשך, שאומר איך אנחנו יכולים בעצם להפעיל פונקציות של Windows שעובדות ממש בשיטה של API של Windows מתוך .NET.
אין לנו נושא נפרד שמטפל בזה, אבל כאן אנחנו צריכים להבין ש-Windows עצמו עובדת בשתי שיטות של טרד, ואנחנו יכולים בתוכנית לשלוט על איזה שיטה אנחנו עובדים עם Windows.
הדבר האחרון שאנחנו הולכים לראות בנוגע ל-threading זה איך אנחנו יכולים לעדכן User Interface על ידי Thread נפרד. מה זאת אומרת לעדכן User Interface על ידי Thread נפרד?
רגע, אנחנו לא עשינו את זה כבר? אז למעשה אנחנו עשינו דבר כזה. בואו נסתכל רגע, נפתח פרויקט שכבר עבדנו עליו. וזה היה בתיקייה myprojects threads וכאן עבדנו על איזשהו פרויקט שבו אנחנו הבנו את ההבדל בין manual reset event לבין auto reset event.
בואו ניקח לדוגמה את ה-manual reset event, נפתח אותו ובדוגמה הזאת אנחנו ראינו שאנחנו יכולים להריץ שני טרדים על ידי לחצן סט ולעשות להם ריסט ועוד פעם סט וריסט וממלאים את הליסטים והכל בסדר אבל מה שאתם לא ראיתם ואיך הרצתי את התוכנית הזאת ומה שעשיתי בשביל להריץ אותה זה לחצתי במקלדת על קונטרול
F5 אם אני מריץ את זה על ידי קונטרול F5 אנחנו רואים שהתוכנית עובדת כמו שצריך אבל מה קורה אם אנחנו מריצים את התוכנית על ידי F5?
זה סטארט דיבאגינג בצורה כזאת או על ידי הרצה של F5 בצורה כזאת. אז בואו נריץ את התוכנית. אנחנו רואים שהטופס רץ, הטרדים פועלים, אני לוחץ על סט ושימו לב שהתוכנית קורסת.
הסיבה שלא ראינו את זה מקודם זה מפני שלא הרצתי את התוכנית על ידי F5. ועכשיו אנחנו צריכים להבין א', למה היא קרסה, ב. למה יש הבדל בין ההרצה על ידי קונטרול F5 לבין F5, האם בכלל זה נכון להריץ את זה על ידי קונטרול F5 ולא על ידי F5, ואם לא, איך בדיוק מתקנים את זה.
אז נתחיל לאט לאט. קודם כל אנחנו רואים שיש פה בעיה שנקראת Cross-thread Operation Not Valid, ואם נקרא נראה שהוא כותב לנו פה: קונטרול LST Odd Numbers accessed from a thread other than the thread it was created on.
אוקיי, קודם כל בואו נבין מה בעצם הוא אומר לנו פה. מה שהוא אומר לנו כאן זה שיש לנו את ה-LST Odd Numbers ומי שמנסה לגעת בו ולהוסיף לו מידע או לקרוא ממנו מידע, לא משנה מה, זה Thread אחר מה-Thread שבו הוא נוצר.
עכשיו בואו נתחיל להבין לאט לאט מה בעצם קורה כאן. הנתון הראשון שאנחנו צריכים להבין זה שטופס מנוהל על ידי טרד נפרד.
כל פעם שיש טופס שרץ יש טרד ייעודי שמנהל את הטופס. מה זה אומר מנהל את הטופס? א', הוא יוצר את כל האובייקטים שקיימים על הטופס.
כל אובייקט שקיים על הטופס, תפתורים, ליסט בוקס, קומבו בוקס, רדיו בטן, לייבלים, טקסט בוקסים, כל מה שקיים על הטופס נוצר בטרד שמנהל את הטופס.
בנוסף, כל האירועים של הטופס מתבצעים בטרד הזה. זה אומר שהאירוע קליק של הלחצן לדוגמה מתבצע בטרד שמנהל את הטופס. עכשיו, בואו נחזור רגע לשרטוט המפורסם שיש לנו ונבין משהו לגבי זה.
אם טופס מנוהל על ידי טרד נפרד אז כאשר הטופס מעדכן את הגרפיקה שלו, צובע את הרכיבים, עושה פעולות על הרכיבים, מריץ את הקוד שלו אז כמובן שהקוד של הטופס נמצא כאן או ליתר דיוק הטרד של הטופס נמצא ב-run time.
זה אומר שהטרד שלנו כרגע לא רץ וממתין ב-runnable אבל אם הטרד שלנו שאנחנו הפעלנו מקבל זמן CPU אז הוא רץ ולא הטרד של הטופס כי כל פעם טרד אחד רק רץ באזור של הרנטיים אז אם הטרד שלנו כרגע רץ ובא ואומר אני רוצה להוסיף עכשיו עכשיו עכשיו ערך לליסטבוקס היחידי שיכול באמת להוסיף ערך לליסטבוקס ולצבוע
לדוגמה את הליסטבוקס ולהוסיף את הפריט אליה ולעדכן את גרפיקה שלה זה הטרד של הטופס אבל הטרד של הטופס לא רץ עם הטרד שלנו פעיל באותו רגע וזאת בעצם אחת הבעיות הבסיסיות שקיימות כאשר טרדים שלנו מנסים לעדכן את היוזר אינטרפייס.
מבחינת מערכת ההפעלה יש סיבות פנימה בתוך מערכת ההפעלה למה בעצם זה לא יכול להתבצע אבל to make long story short החוק אומר אם יש לנו טרד שאנחנו הפעלנו הנה פונקציה שמופעלת על ידי טרד משלנו ולא על ידי הטופס אסור באיסור חמור לפונקציה לגעת בשום רכיב של הטופס.
הסיבה ובדיוק מה שדיברנו כרגע הטופס לא רץ כאשר הפונקציה רצה הוא ממתין באזור של הrunable בתוך מאחורי הקלעים קורים כל מיני דברים שהוא לא יכול כרגע להתפנות בשביל לעדכן את הגרפיקה שלו.
Anyway, החוק אומר אסור לעשות את זה. עכשיו, בואו נדבר על למה קונטרול F5 עובד ו-F5 לא עובד. אז קונטרול F5 זה בעצם הרצה של קובץ האקסקיושן על ידי הלקוח.
זה כאילו שלחצתם דאבל קליק על קובץ האקסקיושן. אם זה קרה, אז כאילו .NET מוותרת לנו. היא אומרת אני אתמודד עם זה אני לא אעדכן לכם את הגרפיקה בדיוק כשאתם רוצים אני אצליח לזכור שאתם רוצים לעדכן את הגרפיקה וכשהטופס יקבל זמן CPU אני אתמודד עם זה.
בפועל זה לא בדיוק קורה כמו שצריך ויכולים להתבצע הרבה דברים שהם לא אוקיי כתוצאה מזה. דברים שהם פחות חמורים שיכולים להתבצע כתוצאה מזה שאנחנו מעדכנים את ה-UI מ-thread נפרד ומריצים את זה על ידי קונטרול F5 או שהלקוח שלנו מריץ את התוכנית ושלדוגמא אם אנחנו צובעים קטע מהטופס בטרד נפרד, פתאום רק חצי נצבע וחצי לא נצבע,
חלק מה-UI מתעדכן וחלק לא, חלק רואים את זה בליסט וחלק לא, זה דברים שהם פחות חמורים. הדברים היותר חמורים, ופתאום רכיבים יכולים להיעלם מהטופס, כפתורים נעלמים, דברים הזויים, אף אחד לא יודע בדיוק מה קורה, למה זה קורה, ודברים שהם ממש הזויים שאי אפשר כל כך לצפות אותם.
לכן החוק עומד בעינו, אנחנו לא רשאים לגעת ב-UI של הטופס מטרד נפרד. וכאשר אנחנו מריצים את זה עם F5, דוטנט אומרת לנו, אה, אתם במצב דיבאגר, ואומר שאתם מתוך ויז'ואל סטודיו, אני אתן לכם קריסה על זה בשביל שוואלה תטפלו בזה.
צריך להגיע למצב כזה ומצב כזה הוא לא נכון אז אנחנו נאלצים עכשיו לדעת איך בדיוק אנחנו יכולים לעדכן את ה-UI מתרד נפרד בצורה שהיא חוקית. אז למעשה ישנן כמה שיטות שמאפשרות לעשות את זה אבל לצורך כך לא נערבב את הפרויקטים אחד עם השני אלא בואו נעשה את הכל בצורה נפרדת ונסתכל על השיטה הראשונה.
השיטה הראשונה נקראת Invoke. בואו נפתח פה פרויקט חדש, נקרא לו Invoke, הוא אינפורם כמובן, ונעשה משהו מאוד פשוט, אפילו בלי Auto-Reset Event ו-Manual-Reset Event כמו שראינו בדוגמה הקודמת.
ניקח לחצן הכי פשוט שימלא ListBox. נקרא לו Fill List, ניקח ListBox, יש לנו פה את הלחצן, יש לנו כאן את ה-ListBox בצורה כזאת, ומה שאנחנו רוצים, אחרי שטיפה נעצב את הטופס כמו שצריך, נגרום לו לעלות במרכז המסך.
אנחנו רוצים למלא את הליסט במספרים פשוטים, נניח מ-0 עד 100, ואנחנו רוצים לעשות את זה בטרד נפרד. ניקח פה טרד, נקרא לפונקציה פי ליסט, נרוץ מ-0 עד 100, ניגש לליסט, ניגש ל-items add ונוסיף את המספר i, אפילו נעשה את זה על ידי insert כך שנראה כל פעם את המספר באינדקס הראשון.
אוקיי, כמובן שאם אני עכשיו אריץ את התוכנית אז בדיוק בנקודה הזאת היא תקרוס. בואו נבדוק את זה. נריץ את התוכנית עם F5, תוכנית רצה. אני בא, מנסה להריץ את התוכנית, אני כבר רואה פה Cross-thread Operation Not Valid.
נעצור. אם היינו מריצים את התוכנית עם Control-F5 כמובן שהיא הייתה רצה בלי שום בעיה. אבל זה לא באמת חוכמה ואין לנו אפילו מה לבדוק את זה, רק בשביל לראות שזה עובד כמו שצריך, כי אנחנו לא רוצים להגיע למצב כזה, זה לא נכון לבצע פעולה כזאת.
מה הפתרון? הפתרון הראשון אומר כזה דבר. בואו נבנה פונקציה, נקרא לה add to list. היא תקבל איזשהו number והפונקציה הזאת תיגש לליסט ותוסיף את המספר.
אוקיי, אבל רגע, מותר לנו לקרוא לה מפה ולשלוח את ה-i? אם אנחנו קוראים לה מכאן, איזה טרד היא מתבצעת אז הטרד שלנו הפעיל את הפונקציה פיל ליסט הפונקציה פיל ליסט הפעילה את עד טו ליסט זה אומר שעד טו ליסט רצה בקונטקסט של הטרד שלנו וזה כמובן לא פותר לנו את הבעיה.
בואו רק נגדיר את t is background true שאם זה ייקח לנו הרבה זמן אז אנחנו נסגור את התופס ואוטומטית הוא ייסגר בואו נריץ רגע את התוכנית עם F5 ונראה שעדיין מתרחשת פה קריסה בול בנקודה הזאת.
בסדר? אז אנחנו יודעים קודם כל שבנינו פונקציה חיצונית שמסוגלת להוסיף פריט לרשימה, אבל אסור לנו לקרוא לה מהטרד שלנו.