מילון מושגים:
אסינכרוני - אופן פעולה שבו פעולה ארוכה רצה ברקע והתוכנית ממשיכה בלי להמתין לה, ומטופלת כשתסתיים.
MVC - דפוס ארכיטקטורה שמפריד יישום לשלושה חלקים: מודל (נתונים), תצוגה (View) ובקר (Controller).
פריימוורק - מסגרת תוכנה שמספקת מבנה וכלים מוכנים לפיתוח יישומים, כדי לחסוך כתיבת קוד חוזר.
תהליכון - יחידת ביצוע בתוך תהליך, המאפשרת להריץ כמה מהלכים במקביל ולחלוק את אותו זיכרון.
מעבד - יחידת העיבוד המרכזית של המחשב, שמבצעת את הפקודות של התוכניות ומנהלת את החישובים.
מאפיין - תכונה שמצמידים לרכיב ומתארת אותו או את התנהגותו, למשל מאפיין של תגית HTML או שדה באובייקט.
URL - הכתובת שמזהה משאב באינטרנט, למשל דף אתר, וכוללת פרוטוקול, שם מארח ונתיב.
באג - תקלה או שגיאה בקוד שגורמת לתוכנה להתנהג בניגוד למצופה.
שימוש ב-Async Controller
בשיעור זה לומדים תחילה תכנות אסינכרוני עם המחלקה Task, רואים את הבעיה של פעולות חוסמות ואת פתרון ה-async ו-await, ולבסוף מיישמים קונטרולר אסינכרוני ב-MVC שמשחרר משאבי שרת לקריאות אחרות.
נושאי הסיכום המוצגים בשיעור:
1. מטרת השיעור: הדבר הבא שרואים הוא שימוש בקונטרולר אסינכרוני, כלומר גלישה לקונטרולר שמתבצע במקביל לשאר הפעילויות בשרת, ולצורך זה צריך קודם להבין איך פועלת המחלקה Task ומהו תכנות אסינכרוני, ורק אחרי ההבנה עוברים לשימוש בקונטרולר האסינכרוני.
2. הקדמה על Task: Task היא מחלקה שהוכרה בגרסת פריימוורק 4.0, ובגרסה 4.5 של הפריימוורק ובשפת C-Sharp 5 הוכר נושא שנקרא תכנות אסינכרוני, ולצורך ההבנה עובדים על פרויקט יישום קונסולה פשוט.
3. Task מול Thread: אפשר היה לפתוח Thread לביצוע פעילות, אבל Thread נחשב כזה שעובד על מעבד אחד בלבד, תופס מעבד אחד ועובד איתו, ומכיוון שהיום במחשבים קיימים מספר מעבדים, המחלקה Task יודעת לנצל טוב יותר את המעבד ויכולה לחלק את המטלה על מספר מעבדים.
4. Task בסיסי בלי ערך מוחזר: יוצרים פונקציה ראשונה ובתוכה אובייקט מטיפוס Task, שהוא מקבל לקונסטרוקטור משהו שנקרא action, שהוא בעצם פונקציה או כתובת של פונקציה שרוצים לבצע, ופונקציית ה-action אינה מקבלת ואינה מחזירה כלום, ובדוגמה הפעולה שלה כוללת השהיה של שלוש שניות כדי לדמות עבודה ארוכה.
5. הפעלה והמתנה: להפעלת ה-task קוראים למתודת ההתחלה שלו, ואז אפשר לבצע פעולה אחרת במקביל, ולבצע המתנה עד שה-task תסתיים בעזרת המתנה מובנית, ובהרצה רואים ששתי הפעולות התבצעו פחות או יותר יחד, התהליך החל במקביל להמשך ריצת התוכנית, ורק אחרי ההמתנה חזרו מהפונקציה לראשיתה.
6. Task שמחזיר ערך: בפונקציה השנייה פותחים task שמחזיר ערך, למשל מחרוזת, בתוך הפונקציה שמורצת ב-task נפרד יש עבודה שלוקחת זמן ובסופה מוחזרת תוצאה, וכדי לקבל את התוצאה קוראים למאפיין התוצאה של ה-task, והקריאה הזאת תבצע בתוכה המתנה, כך שהמערכת תמתין עד שהפעולה מסתיימת והערך חוזר, והערך מנוצל בתוך הפונקציה ואחר כך גם בראשית התוכנית.
7. החזרת הערך גם החוצה: ברוב המקרים רוצים שהפונקציה גם תחזיר את הערך שהיא עובדת איתו, ובפונקציה השלישית מתחילים תהליך במקביל שעושה עבודה ארוכה ומחזיר את תוצאת המטלה, מפעילים את התהליך, עושים משהו אחר בתוך הפונקציה, ממתינים לתוצאה, משתמשים בה בתוך הפונקציה ולבסוף מחזירים אותה החוצה, כך שלא רק הפעלנו פונקציה שביצעה דברים במקביל אלא גם קיבלנו ממנה תוצאה והצגנו אותה אחריה.
8. הבעיה של פעולה חוסמת: מה קורה אם בין הקריאה לפונקציה לבין השימוש בתוצאה רוצים לבצע פעילות שלא קשורה לפונקציה, וכאן יש בעיה, כי עד שלא מקבלים את התוצאה בחזרה הפונקציה לא חוזרת, והקריאה לקבלת התוצאה גורמת לתוכנית להיתקע עד שהיא מסתיימת, מה שנקרא בשפה מקצועית פעולה חוסמת, ובהרצה רואים שהפעילות הלא קשורה בוצעה רק אחרי שהפונקציה הסתיימה במקום להתבצע במקביל אליה.
9. הפתרון הראשון, החזרת task: כדי לקרוא לפונקציה ולרוץ הלאה ולהמתין רק ברגע שצריך את התוצאה עוברים לתחום התכנות האסינכרוני, ובפונקציה הרביעית במקום להחזיר מחרוזת מחזירים task של מחרוזת, בתוכה מתחילים את ה-task, עושים פעילות לא קשורה, ממתינים לתוצאה, משתמשים בה ומחזירים את ה-task עצמו, ומחוץ לפונקציה פותחים משתנה שמקבל את ה-task, עושים פעילות לא קשורה ורק בסוף קוראים לתוצאה שלו.
10. עדיין חוסם: גם כאן קיימת בעיה, מפני שהפונקציה עצמה ממתינה בתוכה לתוצאה של ה-task הפנימי, היא עדיין נחשבת כפעולה חוסמת, והשורה שבה מקבלים את התוצאה מה-task הפנימי מאלצת את כל הפונקציה להמתין, ובהרצה רואים שהפעילות הלא קשורה בראשית התוכנית שוב בוצעה רק אחרי הפונקציה, ואנחנו רוצים שהפונקציה לא תיתקע.
11. הפתרון האמיתי עם async ו-await: בפונקציה החמישית משנים כמה דברים פשוטים, קודם מגדירים את המילה async לפני הערך המוחזר של הפונקציה, מילה חדשה שהוכרה ב-C-Sharp 5, ובמקום לקבל את התוצאה בשורה המוקדמת כותבים את המילה await, ממתינים ל-task עצמו ולא למחרוזת שלו, ורק כשההמתנה הסתיימת מחזירים את התוצאה.
12. מקרה יחיד מסוגו: כאן נקודה מעניינת, הפונקציה עצמה מחזירה task אבל ה-return מחזיר מחרוזת, וזה מקרה יחיד מסוגו שבו פונקציה מחזירה אובייקט אחד וה-return מחזיר אובייקט אחר, וזה ניתן לביצוע רק ב-async עם await בתוך הפונקציה, והערך המוחזר חייב להיות תוצאת ה-task שממתינים לו, אם ה-task מחזיר מחרוזת מחזירים מחרוזת.
13. הבעיה נפתרה: בהרצה רואים שהבעיה נפתרה, שלושת השורות התבצעו יחד, הפונקציה עשתה עבודה במקביל לעבודה הקשה שלה, הראשית המשיכה וביצעה את הפעולה הלא קשורה, והפונקציה הפכה מפעולה חוסמת לפעולה שאינה חוסמת, ורק בסוף כשהגענו לקבלת התוצאה המתינו, וגם השימוש בתוצאה בתוך הפונקציה וגם השימוש בה בראשית התוכנית התבצעו.
14. מה עשו async ו-await: אלו שתי מילים שבאות ביחד, וכל מה שהן עשו הוא שחררו את הפעילות של הפונקציה כך שרצף התוכנית יכול להמשיך, והמתינו ברגע שרוצים להגיע אל ה-task, וברגע שממתינים ל-task המערכת מבצעת את שני הדברים במקביל, מריצה את ה-task, ממתינה עד שיסיים, מאפשרת לראשית התוכנית להמשיך, ורק כשרוצים את התוצאה ממתינים ומקבלים אותה, וברגע שה-task הסתיים התוכנית ממשיכה משני המקומות, וזה בעצם תכנות אסינכרוני.
15. דוגמה מציאותית, הורדת קוד HTML: נבנית פונקציה אסינכרונית שמחזירה task של מחרוזת ותביא קוד HTML מהאינטרנט, היא מקבלת כתובת URL, גולשת אליה, לוקחת את התוכן ומחזירה אותו, ולצורך כך צריך אובייקט שנקרא HTTP Client, שמוסיפים לו רפרנס של מערכת הרשת של דוט נט בגרסת הפריימוורק המתאימה.
16. השימוש ב-HTTP Client: בתוך הפונקציה יוצרים אובייקט מטיפוס HTTP Client שמסוגל להביא קובץ HTML, ויוצרים task של מחרוזת שקורא לפונקציה האסינכרונית המובנית של האובייקט לקבלת תוכן הכתובת, והפונקציה הזאת מקבלת את הכתובת ומחזירה task שכבר בתוכה התחיל התהליך של הורדת קובץ ה-HTML מהכתובת הזאת.
17. הרצה עם אתר כבד: בתוך הפונקציה אפשר לבצע פעולות עד שהמערכת מביאה את ה-HTML, להמתין ב-await לסיום המטלה ולבצע פעולה אחריה, ולבסוף להחזיר את ה-HTML, ובראשית התוכנית קוראים לפונקציה עם כתובת של אתר שנחשב יחסית כבד, ומיד אחרי הקריאה רואים על המסך את הפעולה הלא קשורה, כי הפונקציה אינה חוסמת, ורק אחרי שה-HTML מגיע אחרי שנייה או שתיים מציגים אותו ועושים בו משהו.
18. הרצה והסקת המסקנות: בהרצה רואים שמיד מוצגות הפעולות הלא קשורות ורק אחרי שתיים שלוש שניות מתקבל כל קוד ה-HTML, ובינתיים עד שהוא הגיע ביצענו מספר פעולות במקביל, וכך נסגר העיקרון, קוראים לפונקציה, משחררים את המחשב לדברים אחרים, משחררים את זרימת התוכנית, ובשלב מסוים ממתינים לתוצאה והמחשב רק ממתין.
19. המעבר לקונטרולר אסינכרוני: עכשיו עוברים לקונטרולר האסינכרוני ורואים איך מיישמים את הדברים האלה בתוכו, וזה מתבצע בצורה מאוד פשוטה, מוסיפים קונטרולר חדש לפרויקט.
20. הורשה מ-AsyncController: דבר ראשון, כדי שהקונטרולר שלנו יהיה אסינכרוני הוא חייב לרשת מקונטרולר שנקרא AsyncController, ובתוכו בונים פונקציה אסינכרונית כפי שלמדנו, שמחזירה task של action result, במקום שהפונקציה תחזיר action result כמו שראינו עד היום היא מחזירה task של action result.
21. בניית פעולת החיפוש: נניח שרוצים לבצע חיפוש שלוקח הרבה זמן והפונקציה מקבלת מילת חיפוש, בתוכה מפעילים task שמחזיר רשימה של מחרוזות שהן תוצאות החיפוש, והפעילות שמתבצעת בתוכו לוקחת הרבה זמן, למשל בדוגמה מושהות של שלוש שניות, כמו עבודה של חיפוש, גישה לדאטאבייס וחיפוש דברים שלוקחים זמן.
22. החזרת התוצאות: אחרי שמצאנו את תוצאות החיפוש רוצים להחזיר אותן, בדוגמה מוחזרות מספר תוצאות שכל אחת מהן מורכבת ממילת החיפוש עם מספר, שלמעשה היו מגיעות מהדאטאבייס, ואז מיד מפעילים את ה-task, ממתינים עד שתוצאות החיפוש יסתיימו, וההמתנה מתבצעת על ידי ה-await, ובסוף מחזירים תצוגה עם התוצאות.
23. ה-return החריג בקונטרולר: שימו לב שהפונקציה מחזירה task של action result, אבל בגלל שהפעלנו אותה בצורה אסינכרונית אפשר להגיד למערכת להחזיר action result רגיל ולא task של action result, וה-MVC הולכת לקרוא לפונקציה הזאת בצורה אסינכרונית.
24. היתרון הגדול: היתרון הוא שברגע שה-MVC קוראת לפונקציה היא משחררת את המשאבים שלה לטיפול בקריאות אחרות, ולא לוקחת thread אחד בלבד שמשתמש בפונקציה הזאת.
25. הבהרה חשובה לגבי הזמן: חשוב לציין שאם הפונקציה לוקחת לה שלוש שניות לעבוד היא לא תעבוד פחות משלוש שניות בגלל שעבדנו איתה בצורה אסינכרונית, אם לפונקציה לוקח פרק זמן מסוים זה הפרק זמן שלוקח לה לבצע, ולא יעלנו פה את הזמן של הפונקציה עצמה.
26. מה כן יעלנו: מה שכן יעלנו זה את הזמן של כלל העבודה של השרת, מפני שעכשיו הרבה קריאות יוכלו להשתחרר תוך כדי שהן ממתינות ב-task הזה, לשחרר את ה-thread לעבודה עבור קריאות אחרות, וברגע שמקבלים את התוצאה מהדאטאבייס חוזרים ומחזירים את התוצאה, כך שהפונקציה לא תתקצר מבחינת זמן אבל כלל העבודה יתקצר.
27. מניעת רעב של threads: המערכת לא תסבול ממה שנקרא thread starvation, מצב שאין לנו מספיק threads לשרת את הלקוחות כי הם עסוקים בכל מיני בקשות שונות, ולכן התוכנית נבנית כך שבמהלך ההמתנה ה-thread משוחרר.
28. בניית התצוגה: מנקים את מרחבי השמות ויוצרים תצוגה לפעולת החיפוש, בתצוגה כותבים כותרת של תוצאות חיפוש ומדפיסים בעזרת לולאת foreach בתוך רשימה לא ממוספרת את התוצאות של החיפוש, שנמצאות במודל, ומציגים כל תוצאה.
29. הרצה ראשונה והגדרת הנתיב: בהרצה צריך להגדיר בדיוק את הנתיב שבו גולשים, לקונטרולר החדש ולפונקציית החיפוש, אבל רואים ששכחו להגדיר את הפרמטר, ואם גולשים רק לפונקציה יש המתנה של שלוש שניות ומקבלים את התוצאות אבל אין אפשרות לשלוח פרמטר.
30. תיקון נתיב הניתוב: בהגדרות הניתוב מוסיפים נתיב ייעודי שבו ברירת המחדל של הקונטרולר היא הקונטרולר החדש, ברירת המחדל של הפעולה היא החיפוש, ובמקום המזהה מגדירים פרמטר של מילת חיפוש, וכך מוגדר הנתיב כזה שמתאים לפונקציה, ובהרצה מקבלים את שלוש התוצאות.
31. באג קטן בשם הפרמטר: כשנכנסים לחיפוש עם מילה מסוימת ממתינים שלוש שניות והמילה לא הופיעה בתוצאות, ולאחר בדיקת הפונקציה מתגלה שהשם של הפרמטר נכתב בטעות בצורה משובשת, ואחרי תיקון האיות הרצה נוספת ממתינה שלוש שניות ומקבלת את התוצאות הנכונות עם המילה שחופשנו.
32. המסקנה מההדגמה: הפונקציה או הפעולה התבצעה שלוש שניות, אבל בגלל שירשנו מהקונטרולר האסינכרוני וביצענו את הפונקציה הזאת כפונקציה אסינכרונית, שחררנו את המשאבים של שרת ה-MVC כך שיוכל לטפל בקריאות אחרות, וברגע שסיימנו את המטלה להביא מבסיס הנתונים את מה שרצינו, רק אז ה-thread חזר אלינו, ה-task המשיכה, וקיבלנו שוב זמן מעבד לשחרר את הגלישה ולהחזיר תשובה ללקוח.
33. מתי משתמשים בזה: אם יש לנו בקשות שלוקחות זמן, time-consuming requests, זה עדיף על פני קונטרולר רגיל, ואם אין בקשות כאלה וכל בקשה לוקחת מעט מאוד זמן להתבצע, השימוש בקונטרולר האסינכרוני לא מתאים, כי הוא טיפה יותר מורכב וגם דורש הרבה פעילות של threads במערכת.
34. חלוקת ההמלצה לפי סוג הבקשה: עבור בקשות שמבצעות מהר ולא מתאים, ובקשות שפועלות לאט, מבצעות המון חישובים ומושכות המון מידע מבסיס הנתונים, קונטרולר אסינכרוני הוא הרבה יותר מתאים.
סיכום:
בשיעור זה הבנו את עולם התכנות האסינכרוני מהיסוד, למדנו את המחלקה Task שמנצלת טוב יותר את מעבדי המחשב מול ה-thread הבודד, ראינו את הבעיה של פעולות חוסמות כשצריך תוצאה מוקדם, את הפתרון החלקי של החזרת task ואת הפתרון המלא של async ו-await שמשחררים את זרימת התוכנית וממתינים רק ברגע שצריך את התוצאה, והיישמנו זאת בקונטרולר אסינכרוני ב-MVC שמשחרר את ה-thread בזמן ההמתנה ומונע רעב של threads, ובשיעור הבא נעסוק בייעול עם Bundling ו-Minification.
בשיעור הבא נעסוק בנושא ייעול עם Bundling and Minification.
SR1116HE