
Task<T>.Thread.CurrentThread.ManagedThreadId מראה שה-Task רץ על טרד אמיתי מהמאגר.הטקסט המלא של הדקות הראשונות בהרצאה «Tasks - חלק 1» בקורס הווידאו של PracticU – כתוביות בעברית עם זמן התחלה לכל פסקה. כך תוכלו לקרוא ולחפש את הנושאים לפני שמתחילים ללמוד.
אחרי שראינו מה זה Thread אנחנו עוברים להכיר דבר חדש שנקרא Task. מה זה Task? קודם כל Task בדומה ל-Thread מאפשר לנו לבצע פעולה אסינכרונית, ממש כמו Thread מאפשר לנו לקרוא לפונקציה שאנחנו בונים בצורה אסינכרונית, כלומר בצורה מקבילה לשאר התוכנית.
אבל Task משתמש ב-Thread שקיים במאגר Threadים, כלומר ב-Thread Pool. יצא לעולם בדוטנט 4.0. אז מה בעצם ההבדל בין Thread לבין Task?
בואו נראה. יש לנו מספר דברים לגבי Thread ומספר דברים לגבי Task שאנחנו הולכים לראות ולהשוות את ההבדלים ביניהם. דבר ראשון, כאשר אתם פותחים Thread, אז אתם מקבלים ממערכת ההפעלה Thread שהוא Dedicated עבורכם, הוא ייעודי עבורכם.
כלומר, מי שבאמת יש לו את ה-Thread ומחזיק את ה-Thread, זאת מערכת ההפעלה, אתם ביקשתם אובייקט טרד, ומערכת ההפעלה מקצה לכם טרד.
כאשר אתם רוצים להשתמש ב-Task, אז אתם לא באמת משתמשים, כלומר אתם משתמשים בטרד כמובן מאחורי הקלעים, אבל מערכת ההפעלה לא מקצה עבורכם איזשהו טרד ייעודי עבור הפעילות שלכם, אלא קיים מאגר של טרדים עם המון המון המון המון טרדים, ופה אנחנו הולכים לראות למה לפעמים זה יותר יעיל מבחינתנו.
אז כאשר אתם בעצם רוצים task, המערכת נותנת לכם את אחד מהטרדים שנמצאים בתוך המאגר הזה, ומערכת ההפעלה לא יוצרת עבורכם איזשהו טרד. המאגר הזה, ה-Trade Pool, כבר מכיל טרדים, אובייקטי טרד, מוכנים לריצה, הוקצו מבחינת מערכת ההפעלה ומוכנים עבורכם לרוץ.
כאשר אתם משתמשים בטרד, אתם צורכים יותר resources, כאשר אתם מבקשים במערכת ההפעלה, תביא לי בבקשה טרד ייעודי, אז כמובן זה מגיע עם כל מיני משאבים נוספים.
הטרד מוקצה מבחינת מערכת ההפעלה, אובייקט מוקצה מבחינתכם, יש לכם 1 מגה בייט של סטאק עבור הפונקציה שהטרד מפעיל, שהוא שונה מסטאקים אחרים, כלומר יש פה יותר משאבים שאתם זוללים כאשר אתם משתמשים בטרד ייעודי.
כאשר אתם משתמשים בטאסק יש פחות משאבים, יש המון דברים שמשותפים לטרד פול ולכן אתם לא זוללים משאבים ייעודיים עבור הטרד שלכם. כאשר אתם משתמשים בטרד, זמן יצירת הטרד מבחינת מערכת ההפעלה לוקח זמן ולכן יש לכם פה זמן נוסף שמתווסף כאשר אתם יוצרים טרד משלכם.
מבחינת טאסק יש פה פחות זמן כי הטרדים כבר מוכנים לריצה, אף אחד לא יוצר אותם במיוחד בשבילכם. כאשר אתם משתמשים בטרד זה לא תמיד הדבר שהוא מבוצע אופטימלי יחסית למספר CPUs.
יכול להיות שיהיה לכם מספר CPUs או מספר ליבות של CPUs של מעבדים של המחשב ויש לכם נניח עשרה טרדים שכולם יטפלו ל-CPU אחד.
בדרך כלל מערכת ההפעלה מצליחה לבצע את האופטימיזציה הזאת אבל זה לא בהכרח יהיה. כאשר אתם משתמשים ב-Task יש לכם אופטימיזציה אוטומטית שמתבצעת על ידי המערכת מבחינת הליבות של מעבד המחשב.
אם אתם משתמשים בטרד בגלל שיש לכם טרד של מערכת ההפעלה עצמה שהוא dedicated עבורכם יש לכם שליטה על כל מיני פרופרטיב של הטרד הזה. לפעמים אנחנו רוצים להגדיר לו שם לפעמים אנחנו רוצים לגרום לטרד הזה להיות משותף עם מספר אפליקציות שונות לפעמים אנחנו רוצים לדעת מה ה-ID שלו וכולי.
אז יש לנו פה שליטה בגלל שיש לנו טרד משלנו זה בעצם יתרון מסוים שיש לטרד על פני טאסק. אם אתם משתמשים בטאסק יש לנו פה פחות שליטה על פרופרטיב של ה-Low-Level-Tread של מערכת ההפעלה כי בעצם אין לנו ממש גישה לטרד הזה.
אם אנחנו משתמשים בטרד ואנחנו קוראים לפונקציה הפונקציה שטרד מפעיל לא יכולה להחזיר לנו ערך היא יכולה להיות כזאת שמקבלת void כלומר לא מקבלת דבר ולא מחזירה דבר או מקבלת אובייקט בודד ולא מחזירה דבר אבל היא לא יכולה להחזיר ערך על ידי פקודת ריטר טסק לעומת זאת
כן אנחנו יכולים לקרוא איתה לפונקציה שמחזירה ערך וזה יתרון ממש מגניב אז מתי אנחנו משתמשים בטרד ומתי אנחנו משתמשים בטסק טרד זה טוב עבור פעולות ארוכות יחסית או אם אנחנו רוצים לבצע איזושהי שליטה על הפרופרטיז של הקרנל טרד שמוקצה לנו עכשיו זאת נקודה חשובה אם אנחנו לוקחים פעולות ארוכות
עבור task אז יכול להיות מצב שפשוט מאוד כל הטרדים במאגר טרדים ייגמר כי הוא משרת את הפעולות שלנו ובעצם פעולות נוספות שאנחנו רוצים לבצע יצטרכו להמתין עד שטרדים במאגר ישתחררו וזה פחות יעיל אז task יותר מתאים עבור פעולות קצרות יחסית שלוקחות לא הרבה זמן וכמובן
כאשר אנחנו לא רוצים שום שליטה על המאפיינים של ה-thread של מערכת ההפעלה שנוצר עבורנו. אז אחרי שראינו את כל ההבדלים האלה, בואו נראה דוגמה.
הדבר הראשון, אנחנו הולכים לפתוח אובייקט מטיפוס Task. Task נמצאת במרחב השמות System.threading.tasks. אנחנו רואים שאנחנו מצפים לקבל פונקציה, ויש לנו פה פונקציה ש-Action יכול להצביע עליה.
Action, כזכור, זה Delegate, ומסוגל להצביע על פונקציה שלא מקבלת כלום ולא מחזירה כלום. זאת אומרת שאנחנו צריכים לבנות פונקציה כזאת.
אז בואו נבנה פונקציה, נקרא לה doWork. יש לנו פה static void doWork ונעשה פה איזושהי פעולה שכמובן זוללת זמן כי אחרת אין לנו טעם להשתמש או ב-thread או ב-task.
אז בואו נלך פה על איזושהי לולאה שרצה 10 פעמים, מבצעת המתנה נניח חצי שנייה ובואו נגיד אנחנו רוצים לסכום כאן איזשהו ערך ובסוף להציג את הערך בגלל שהפעילות הזאת לוקחת הרבה זמן אז אנחנו רוצים לבצע עוד דברים במקביל ולכן אנחנו רוצים להפעיל את זה עם task ולשחרר את שאר התוכנית לבצע פעולות נוספות לדוגמה כאן אני רושם doing something else כמו
thread גם כאן אנחנו צריכים לבצע start ולהפעיל את הtask שלנו עכשיו אם אנחנו נריץ את זה אנחנו נראה משהו מאוד מעניין בואו נריץ את התוכנית רולף 5 ומה שאנחנו הולכים לראות זה כאילו הטאסק לא התחילה בכלל אבל למעשה משהו אחר קרה כאן אם אתם זוכרים בעולם הטרדים קיים background thread ו-
foreground thread כאשר אנחנו פותחים אובייקט thread רגיל הוא אוטומטית מוגדר כ- foreground כאשר אתם לוקחים thread מה-threadpool הוא אוטומטית מוגדר כ-background עכשיו אם נזכר מה ההבדל נראה ש-Foreground Thread סך הכל זה טרד שגורם לפרוסס עצמו להמשיך לרוץ כל עוד הטרד פעיל
Background Thread זה טרד שפשוט לא גורם להמתנה עבור הפרוסס ואם הפרוסס נסגר, כלומר אין יותר Foreground Thread שרצים, אז אוטומטית הפרוסס נסגר אז הוא אוטומטית הורג איתו את כל ה-Background Threads בגלל זה כאשר המיין נסגר אוטומטית כל ה-Background
Thread מתו ואיתם מת גם ה-Task שלנו שמפעיל Background Thread מאחורי הקלעים. אם היינו רושמים פה איזשהו console.reline והדבר הזה כמובן גורם להמתנה של התוכנית אנחנו יכולים להריץ את זה ולראות סם שווה איזה 45 והכל בסדר.
אנחנו יכולים אפילו לבצע פה איזושהי הדפסה של i ואז לראות את זה תוך כדי מוצג לנו. אז אנחנו יכולים לראות שלמרות שהתחלנו בעצם את התוכנית שלנו, את הטרד שלנו והוא התחיל לרוץ, הוא התבצע בצורה א-סינכרונית וכאשר הוא התבצע אנחנו המשכנו את התוכנית, הצגנו את הדבר הזה וזה בעצם הוכחה לכך שהכל התבצע ביחד.
אנחנו יכולים לבצע המתנה t.wait זאת פונקציה שממתינה או שליתר דיוק מקבילה לפונקציה join שהייתה שייכת לטרד כאן המיין בעצם מבצע המתנה והמיין ייכנס לאזור הסליפ עד שהטאסק t תסתיים בואו נריץ את זה ונראה בעצם שעדיין יש לנו פה המתנה אבל אנחנו לא צריכים ללחוץ על מקש ברגע שזה יסתיים אוטומטית התוכנית שלנו תסתיים עכשיו כאשר הרצנו את כל הסיפור הזה כמובן מאחורי הטאסק קיים איזשהו טרד מתרד פול
ואנחנו יכולים אפילו להגיע אליו ולראות כל מיני פרמטרים בנוגע אליו. אז איך אנחנו יכולים לעשות את זה? בואו נשנה פה את ההדפסה כך שנדפיס right line ואז נדפיס כזה דבר i שווה לערך ה-i ו-managed thread ID שווה ל-managed thread ID שזה בעצם איזשהו ID שה.NET נותנת עבור ה-thread
האמיתי שלנו. אז אנחנו יכולים להגיע ל-i, פשוט על ידי כתיבת i, ול-manage thread ID על ידי thread.currentthread זה ה-thread הנוכחי שקיים מה-thread pool, ואנחנו יכולים להגיע ל-manage thread ID ולראות את ה-ID שלו.
כך אנחנו בעצם יכולים לראות שבאמת קיים כאן thread אמיתי שמשרת אותנו. הוא פשוט לא הוקצה עבורנו dedicated, אלא נלקח מה-thread pool, יש לזה כמו שראינו את היתרונות ואת החסרונות של הדבר הזה.
בואו נריץ את זה, ואנחנו יכולים לראות שהוקצה עבורנו thread שה-manage thread ID שלו זה שלוש. מה נחלט? יש לנו דרך נוספת להתחיל task, במקום ליצור אובייקט ואז לקרוא ל-t.start אנחנו יכולים להשתמש בצורה הבאה task.t=task.factory כלומר בוא תייצר לנו New Task והוא מקבל כמובן את Do Work לצורך
פעולה. ברגע שעשינו את זה קרו שני דברים. אוטומטית נוצר לנו אובייקט Task חדש שמצביע לפונקציית Do Work ואוטומטית הוא התחיל אנחנו לא צריכים לקרוא לפונקציית Start.
בואו נריץ ונראה שאוטומטית הכל מתחיל כמו שראינו מקודם. ניתן לזה להסתיים, נסגור ונחזור בחזרה.
אז אחרי שראינו מה זה Task בואו נראה כל מיני יכולות נוספות שקיימות לטאסק ומאפשרות לנו לעשות הרבה דברים שחלק מהם בכלל לא היינו יכולים לעשות כאשר היינו משתמשים בטרדס היינו צריכים לעשות כל מיני וורק אראונד רק בשביל להצליח לעשות את זה ובטאסק יש לנו את היכולות האלה אז לצורך כך בואו עבור כל יכולת נפתח
תוכנית אחרת לא נערבב בין התוכניות שיהיה ממש ממש ברור חלק מהקוד אני אצטרך אז אני אבצע קופי פייסט בואו נסגור את התוכנית הנוכחית נפתח תוכנית חדשה כל התרגילים האלה כמובן יש לכם אותם והם זמינים עבורכם ומה שאנחנו רוצים לראות זה איך אנחנו יכולים לשלוח פרמטר לפונקציה בואו נפתח את זה נדביק את הפונקציה שלנו כאן ואנחנו רוצים להציב עליה task הנה
יש לנו פה את ה-object task הוא מצביע לפונקציה do work ואנחנו מפעילים אותו מבצעים משהו אחר וממתינים לו task יכולה לקרוא לפונקציה