PracticU

Threads - חלק 8

בלי כתוביותעם כתוביות
PracticU › קורס תכנות מתקדם - #Advanced .NET C › Threads - חלק 8
מילון מושגים:
.NET - פלטפורמת פיתוח של מיקרוסופט להרצת יישומים בשפות כמו C#, הכוללת ספריות וסביבת ריצה משותפות.
API - ממשק תכנותי שמגדיר כיצד תוכנה אחת פונה לאחרת ומחליפה איתה נתונים ופעולות.
חריגה - מצב שגיאה שמתרחש בזמן ריצת התוכנית ומפסיק את המהלך הרגיל, אלא אם הקוד תופס ומטפל בו.
טופס - אזור בממשק שבו המשתמש מזין נתונים בשדות ושולח אותם, למשל טופס הרשמה או התחברות.
רכיב - יחידת בניין עצמאית וניתנת לשימוש חוזר בתוך מערכת או ממשק משתמש, שאחראית על חלק מוגדר מהתפקוד.
אירוע - התרחשות שהמערכת מזהה ויכולה להגיב לה, כמו לחיצת עכבר, הקשה או סיום פעולה.
תהליכון - יחידת ביצוע בתוך תהליך, המאפשרת להריץ כמה מהלכים במקביל ולחלוק את אותו זיכרון.
לולאה - מבנה בקרה שחוזר על קטע קוד שוב ושוב כל עוד תנאי מתקיים או עבור כל פריט באוסף.

Threads - חלק 8
השיעור האחרון בנושא הטרדים עוסק בשני דברים: מודלי ה-apartment של Windows, STA ו-MTA, ועדכון ממשק המשתמש מטרד נפרד.
STA ו-MTA
1. MTA: מערכת ההפעלה פותחת טרד נפרד לכל פעולה. .NET עובד כברירת מחדל במודל MTA.
2. STA: טרד אחד מריץ סדרה של פעולות לפי הסדר. חלק מפונקציות ה-API של Windows דורשות מודל STA.
3. המעבר ל-STA: אפשר לסמן את ה-Main בתכונה STAThread, או לקבוע את המצב לטרד בודד בעזרת SetApartmentState. מומלץ לא להעביר את כל האפליקציה ל-STA, אלא רק טרד שקורא לפונקציה שדורשת זאת.
4. הסימן לבעיה: אם מקבלים חריגה שמזכירה STA או MTA, סביר שפונקציה של Windows דורשת STA והטרד הנוכחי נמצא ב-MTA.
עדכון ממשק המשתמש מטרד נפרד
5. טרד הטופס: טופס מנוהל על ידי טרד משלו. הוא יוצר את כל הרכיבים ומריץ את כל האירועים. כשטרד אחר מנסה לגעת ב-ListBox מתקבלת השגיאה Cross-thread operation not valid.
6. F5 ו-Ctrl+F5: ההרצה עם F5 מתבצעת תחת הדיבאגר והשגיאה מופיעה מיד. ההרצה עם Ctrl+F5 לא מתנהגת בדיוק כך, אבל ההתנהגות שגויה בשני המקרים. הכלל הוא לא לגעת ב-UI מטרד נפרד.
7. Invoke: שולח דלגייט לטרד הטופס וממתין עד שהוא מבצע אותו.
8. BeginInvoke: שולח את הדלגייט בלי להמתין, וכך לא תוקעים את הטרד השולח.
9. InvokeRequired: מחזיר true כשהקוד רץ מחוץ לטרד ה-UI. בעזרתו בודקים אם צריך להשתמש ב-Invoke או אפשר לעדכן את הרכיב ישירות.
10. SynchronizationContext: שומר את הקשר הטרד של ה-UI ומאפשר לשלוח אליו פעולה ממקום אחר. זה מתאים כשהשרשרת עוברת בין כמה DLLים עד שמגיעים לעדכון.
11. BackgroundWorker: רכיב עם שלושה אירועים. DoWork רץ בטרד נפרד ואסור בו לגעת ב-UI. ProgressChanged רץ בטרד ה-UI ומעודכן בעזרת ReportProgress. RunWorkerCompleted רץ בטרד ה-UI ומקבל את התוצאה ב-e.Result. הדגלים WorkerReportsProgress ו-WorkerSupportsCancellation מאפשרים דיווח והפסקה. הביטול נעשה בעזרת CancelAsync ובדיקת CancellationPending בתוך הלולאה.
הכלל בכל הדרכים זהה: הטרד הנפרד מחשב, וטרד ה-UI הוא זה שמעדכן.
השיעור הבא בקורס: תרגילים על Threads

פרקי ההרצאה: Threads - חלק 8

  1. 00:00

    STA ו-MTA בעבודה עם Threads

  2. 01:31

    מעבר בין MTA ל-STA בקוד

  3. 04:56

    עדכון UI מ-Thread נפרד

  4. 07:48

    שגיאת Cross Thread Operation

  5. 08:35

    למה הטופס מנוהל ב-Thread ייעודי

  6. 12:09

    ההבדל בין הרצה ב-F5 ל-Control F5

  7. 14:33

    פתרון עם Invoke

תמלול ההרצאה: Threads - חלק 8

הטקסט המלא של הדקות הראשונות בהרצאה «Threads - חלק 8» בקורס הווידאו של PracticU – כתוביות בעברית עם זמן התחלה לכל פסקה. כך תוכלו לקרוא ולחפש את הנושאים לפני שמתחילים ללמוד.

  1. 00:04

    הדבר הבא שכדאי להכיר בעולם הטרדים זה שני מונחים שנקראים STA ו-MTA. בואו נסתכל מה בעצם הם אומרים. יש לנו פה תוכנית חדשה, Console Application, STA ו-MTA.

  2. 00:30

    אז המונח STA אומר Single Thread Apartment ו-MTA אומר Multi Thread Apartment. מה שזה אומר זה בעצם שיטה שמערכת ההפעלה עובדת עם טרדים.

  3. 00:55

    למערכת ההפעלה יש שתי אפשרויות לעבוד עם טרדים. MTA זה בעצם השיטה שבה מערכת ההפעלה פותחת טרד נפרד לפעולה מסוימת. STA זה אומר שמערכת ההפעלה פותחת טרד ונותנת לטרד הזה כל מיני פעולות לפי הסדר.

  4. 01:19

    .NET עובדת by default כ-MTA אבל יכול להיות מצב שאנחנו ב-Dotnet מפעילים איזה משהו שמבחינת מערכת ההפעלה הוא עובד בשיטת STA ולכן אנחנו מ-Dotnet חייבים לעבור לשיטת ה-STA ויש לנו שתי אפשרויות לבצע את המעבר הזה מ-MTA ל-STA אפשרות אחת זה על ידי אטריביוט sta-thread מעל המיין וזה אומר שמעכשיו

  5. 01:56

    אנחנו בשיטת sta או הדיפולט זה mta-thread וזה אומר שאנחנו בשיטת ה-mta אנחנו כמובן יכולים לעשות את זה מעל פונקציה מסוימת. אפשרות אחרת אם בתוך הקוד בשלב מסוים אנחנו רוצים לבצע את המעבר הזה אז אנחנו יכולים להשתמש ב-thread נקודה זאת בעצם פונקציה שהיא לא או סטטית אז בואו נפתח פה איזשהו אובייקט טרד,

  6. 02:43

    נניח שיש לנו פה איזושהי פונקציה, אז יש לנו את הפונקציה set apartment state והיא מקבלת enum שאומר שאנחנו mta או sta בצורה כזאת ואז ספציפית את הטרד הזה העברנו למצב שבו הוא עובד כשיטת ה-sta של windows ואז הוא יכול לדוגמה בתוך הפונקציה שהוא מפעיל להפעיל פונקציות שעובדות בשיטת ה-STA מבחינת Windows.

  7. 03:30

    אז כאן אם בעצם אנחנו נתקלים במצב שבו אנחנו רוצים להשתמש עם איזושהי פונקציית מערכת של Windows ופתאום אנחנו חוטפים איזשהו exception וה-exception מדבר על STA או MTA או Single Thread Apartment או Multi Thread Apartment בדרך כלל זה יקרה כאשר פונקציה של Windows הכוונה הפונקציה של ה-API של

  8. 03:58

    Windows עובדת רק בשיטת STA. אנחנו בדוטנט נמצאים בשיטת MTA, כלומר טרדים רגילים, ואז אנחנו חייבים לעשות סוויץ' ולעבור לשיטת STA.

  9. 04:12

    אם זה המצב ואתם חוטפים את האקספצ'ן הזה, אתם יודעים עכשיו איך לבצע את המעבר הזה. לא מומלץ להעביר את כל האפליקציה לשיטת STA כמובן, אלא מומלץ לפתוח איזשהו טרד, להגדיר שהוא יהיה בשיטת ה-STA, ואז שהטרד הזה יקרא לפונקציה של Windows.

  10. 04:32

    אני לא רוצה להיכנס כרגע לדוגמאות, כי אז בעצם אני נכנס לנושא שאנחנו נגיע אליו בהמשך, שאומר איך אנחנו יכולים בעצם להפעיל פונקציות של Windows שעובדות ממש בשיטה של API של Windows מתוך .NET.

  11. 04:51

    אין לנו נושא נפרד שמטפל בזה, אבל כאן אנחנו צריכים להבין ש-Windows עצמו עובדת בשתי שיטות של טרד, ואנחנו יכולים בתוכנית לשלוט על איזה שיטה אנחנו עובדים עם Windows.

  12. 05:07

    הדבר האחרון שאנחנו הולכים לראות בנוגע ל-threading זה איך אנחנו יכולים לעדכן User Interface על ידי Thread נפרד. מה זאת אומרת לעדכן User Interface על ידי Thread נפרד?

  13. 05:22

    רגע, אנחנו לא עשינו את זה כבר? אז למעשה אנחנו עשינו דבר כזה. בואו נסתכל רגע, נפתח פרויקט שכבר עבדנו עליו. וזה היה בתיקייה myprojects threads וכאן עבדנו על איזשהו פרויקט שבו אנחנו הבנו את ההבדל בין manual reset event לבין auto reset event.

  14. 06:01

    בואו ניקח לדוגמה את ה-manual reset event, נפתח אותו ובדוגמה הזאת אנחנו ראינו שאנחנו יכולים להריץ שני טרדים על ידי לחצן סט ולעשות להם ריסט ועוד פעם סט וריסט וממלאים את הליסטים והכל בסדר אבל מה שאתם לא ראיתם ואיך הרצתי את התוכנית הזאת ומה שעשיתי בשביל להריץ אותה זה לחצתי במקלדת על קונטרול

  15. 06:41

    F5 אם אני מריץ את זה על ידי קונטרול F5 אנחנו רואים שהתוכנית עובדת כמו שצריך אבל מה קורה אם אנחנו מריצים את התוכנית על ידי F5?

  16. 06:56

    זה סטארט דיבאגינג בצורה כזאת או על ידי הרצה של F5 בצורה כזאת. אז בואו נריץ את התוכנית. אנחנו רואים שהטופס רץ, הטרדים פועלים, אני לוחץ על סט ושימו לב שהתוכנית קורסת.

  17. 07:20

    הסיבה שלא ראינו את זה מקודם זה מפני שלא הרצתי את התוכנית על ידי F5. ועכשיו אנחנו צריכים להבין א', למה היא קרסה, ב. למה יש הבדל בין ההרצה על ידי קונטרול F5 לבין F5, האם בכלל זה נכון להריץ את זה על ידי קונטרול F5 ולא על ידי F5, ואם לא, איך בדיוק מתקנים את זה.

  18. 07:47

    אז נתחיל לאט לאט. קודם כל אנחנו רואים שיש פה בעיה שנקראת Cross-thread Operation Not Valid, ואם נקרא נראה שהוא כותב לנו פה: קונטרול LST Odd Numbers accessed from a thread other than the thread it was created on.

  19. 08:08

    אוקיי, קודם כל בואו נבין מה בעצם הוא אומר לנו פה. מה שהוא אומר לנו כאן זה שיש לנו את ה-LST Odd Numbers ומי שמנסה לגעת בו ולהוסיף לו מידע או לקרוא ממנו מידע, לא משנה מה, זה Thread אחר מה-Thread שבו הוא נוצר.

  20. 08:33

    עכשיו בואו נתחיל להבין לאט לאט מה בעצם קורה כאן. הנתון הראשון שאנחנו צריכים להבין זה שטופס מנוהל על ידי טרד נפרד.

  21. 08:48

    כל פעם שיש טופס שרץ יש טרד ייעודי שמנהל את הטופס. מה זה אומר מנהל את הטופס? א', הוא יוצר את כל האובייקטים שקיימים על הטופס.

  22. 09:06

    כל אובייקט שקיים על הטופס, תפתורים, ליסט בוקס, קומבו בוקס, רדיו בטן, לייבלים, טקסט בוקסים, כל מה שקיים על הטופס נוצר בטרד שמנהל את הטופס.

  23. 09:20

    בנוסף, כל האירועים של הטופס מתבצעים בטרד הזה. זה אומר שהאירוע קליק של הלחצן לדוגמה מתבצע בטרד שמנהל את הטופס. עכשיו, בואו נחזור רגע לשרטוט המפורסם שיש לנו ונבין משהו לגבי זה.

  24. 09:44

    אם טופס מנוהל על ידי טרד נפרד אז כאשר הטופס מעדכן את הגרפיקה שלו, צובע את הרכיבים, עושה פעולות על הרכיבים, מריץ את הקוד שלו אז כמובן שהקוד של הטופס נמצא כאן או ליתר דיוק הטרד של הטופס נמצא ב-run time.

  25. 10:08

    זה אומר שהטרד שלנו כרגע לא רץ וממתין ב-runnable אבל אם הטרד שלנו שאנחנו הפעלנו מקבל זמן CPU אז הוא רץ ולא הטרד של הטופס כי כל פעם טרד אחד רק רץ באזור של הרנטיים אז אם הטרד שלנו כרגע רץ ובא ואומר אני רוצה להוסיף עכשיו עכשיו עכשיו ערך לליסטבוקס היחידי שיכול באמת להוסיף ערך לליסטבוקס ולצבוע

  26. 10:42

    לדוגמה את הליסטבוקס ולהוסיף את הפריט אליה ולעדכן את גרפיקה שלה זה הטרד של הטופס אבל הטרד של הטופס לא רץ עם הטרד שלנו פעיל באותו רגע וזאת בעצם אחת הבעיות הבסיסיות שקיימות כאשר טרדים שלנו מנסים לעדכן את היוזר אינטרפייס.

  27. 11:10

    מבחינת מערכת ההפעלה יש סיבות פנימה בתוך מערכת ההפעלה למה בעצם זה לא יכול להתבצע אבל to make long story short החוק אומר אם יש לנו טרד שאנחנו הפעלנו הנה פונקציה שמופעלת על ידי טרד משלנו ולא על ידי הטופס אסור באיסור חמור לפונקציה לגעת בשום רכיב של הטופס.

  28. 11:45

    הסיבה ובדיוק מה שדיברנו כרגע הטופס לא רץ כאשר הפונקציה רצה הוא ממתין באזור של הrunable בתוך מאחורי הקלעים קורים כל מיני דברים שהוא לא יכול כרגע להתפנות בשביל לעדכן את הגרפיקה שלו.

  29. 12:01

    Anyway, החוק אומר אסור לעשות את זה. עכשיו, בואו נדבר על למה קונטרול F5 עובד ו-F5 לא עובד. אז קונטרול F5 זה בעצם הרצה של קובץ האקסקיושן על ידי הלקוח.

  30. 12:22

    זה כאילו שלחצתם דאבל קליק על קובץ האקסקיושן. אם זה קרה, אז כאילו .NET מוותרת לנו. היא אומרת אני אתמודד עם זה אני לא אעדכן לכם את הגרפיקה בדיוק כשאתם רוצים אני אצליח לזכור שאתם רוצים לעדכן את הגרפיקה וכשהטופס יקבל זמן CPU אני אתמודד עם זה.

  31. 12:44

    בפועל זה לא בדיוק קורה כמו שצריך ויכולים להתבצע הרבה דברים שהם לא אוקיי כתוצאה מזה. דברים שהם פחות חמורים שיכולים להתבצע כתוצאה מזה שאנחנו מעדכנים את ה-UI מ-thread נפרד ומריצים את זה על ידי קונטרול F5 או שהלקוח שלנו מריץ את התוכנית ושלדוגמא אם אנחנו צובעים קטע מהטופס בטרד נפרד, פתאום רק חצי נצבע וחצי לא נצבע,

  32. 13:14

    חלק מה-UI מתעדכן וחלק לא, חלק רואים את זה בליסט וחלק לא, זה דברים שהם פחות חמורים. הדברים היותר חמורים, ופתאום רכיבים יכולים להיעלם מהטופס, כפתורים נעלמים, דברים הזויים, אף אחד לא יודע בדיוק מה קורה, למה זה קורה, ודברים שהם ממש הזויים שאי אפשר כל כך לצפות אותם.

  33. 13:39

    לכן החוק עומד בעינו, אנחנו לא רשאים לגעת ב-UI של הטופס מטרד נפרד. וכאשר אנחנו מריצים את זה עם F5, דוטנט אומרת לנו, אה, אתם במצב דיבאגר, ואומר שאתם מתוך ויז'ואל סטודיו, אני אתן לכם קריסה על זה בשביל שוואלה תטפלו בזה.

  34. 14:02

    צריך להגיע למצב כזה ומצב כזה הוא לא נכון אז אנחנו נאלצים עכשיו לדעת איך בדיוק אנחנו יכולים לעדכן את ה-UI מתרד נפרד בצורה שהיא חוקית. אז למעשה ישנן כמה שיטות שמאפשרות לעשות את זה אבל לצורך כך לא נערבב את הפרויקטים אחד עם השני אלא בואו נעשה את הכל בצורה נפרדת ונסתכל על השיטה הראשונה.

  35. 14:38

    השיטה הראשונה נקראת Invoke. בואו נפתח פה פרויקט חדש, נקרא לו Invoke, הוא אינפורם כמובן, ונעשה משהו מאוד פשוט, אפילו בלי Auto-Reset Event ו-Manual-Reset Event כמו שראינו בדוגמה הקודמת.

  36. 14:57

    ניקח לחצן הכי פשוט שימלא ListBox. נקרא לו Fill List, ניקח ListBox, יש לנו פה את הלחצן, יש לנו כאן את ה-ListBox בצורה כזאת, ומה שאנחנו רוצים, אחרי שטיפה נעצב את הטופס כמו שצריך, נגרום לו לעלות במרכז המסך.

  37. 15:57

    אנחנו רוצים למלא את הליסט במספרים פשוטים, נניח מ-0 עד 100, ואנחנו רוצים לעשות את זה בטרד נפרד. ניקח פה טרד, נקרא לפונקציה פי ליסט, נרוץ מ-0 עד 100, ניגש לליסט, ניגש ל-items add ונוסיף את המספר i, אפילו נעשה את זה על ידי insert כך שנראה כל פעם את המספר באינדקס הראשון.

  38. 17:06

    אוקיי, כמובן שאם אני עכשיו אריץ את התוכנית אז בדיוק בנקודה הזאת היא תקרוס. בואו נבדוק את זה. נריץ את התוכנית עם F5, תוכנית רצה. אני בא, מנסה להריץ את התוכנית, אני כבר רואה פה Cross-thread Operation Not Valid.

  39. 17:31

    נעצור. אם היינו מריצים את התוכנית עם Control-F5 כמובן שהיא הייתה רצה בלי שום בעיה. אבל זה לא באמת חוכמה ואין לנו אפילו מה לבדוק את זה, רק בשביל לראות שזה עובד כמו שצריך, כי אנחנו לא רוצים להגיע למצב כזה, זה לא נכון לבצע פעולה כזאת.

  40. 17:53

    מה הפתרון? הפתרון הראשון אומר כזה דבר. בואו נבנה פונקציה, נקרא לה add to list. היא תקבל איזשהו number והפונקציה הזאת תיגש לליסט ותוסיף את המספר.

  41. 18:15

    אוקיי, אבל רגע, מותר לנו לקרוא לה מפה ולשלוח את ה-i? אם אנחנו קוראים לה מכאן, איזה טרד היא מתבצעת אז הטרד שלנו הפעיל את הפונקציה פיל ליסט הפונקציה פיל ליסט הפעילה את עד טו ליסט זה אומר שעד טו ליסט רצה בקונטקסט של הטרד שלנו וזה כמובן לא פותר לנו את הבעיה.

  42. 18:46

    בואו רק נגדיר את t is background true שאם זה ייקח לנו הרבה זמן אז אנחנו נסגור את התופס ואוטומטית הוא ייסגר בואו נריץ רגע את התוכנית עם F5 ונראה שעדיין מתרחשת פה קריסה בול בנקודה הזאת.

  43. 19:06

    בסדר? אז אנחנו יודעים קודם כל שבנינו פונקציה חיצונית שמסוגלת להוסיף פריט לרשימה, אבל אסור לנו לקרוא לה מהטרד שלנו.

לקורס המלא: קורס תכנות מתקדם ב‑#C ודוט-נט

ההרצאה הקודמת: Threads - חלק 7

ההרצאה הבאה: תרגילים על Threads

הרצאות קשורות מקורסים אחרים

  • Threads

קורסים נוספים במסלול

  • קורס WinForms - תוכנות רגילות בווינדוס
  • פרויקט מעשי ב-#C - בונים מחשבון פשוט
  • פרויקט מעשי ב-#C - ספר טלפונים
  • פרויקט מעשי ב-#C - תוכנת צייר
  • קורס ADO.NET - חיבור אתר למסד נתונים
  • קורס WCF - תקשורת למתקדמים בדוט-נט