RSS

תכנות מקבילי או אסינכרוני?

01 אוק

תכנות מקבילי + אסינכרוני = תוכנה טובה יותר

 

דוט-נט 4.5 הביא איתו את 5.0 #C שהביא לנו את async & await. שיפורים אלו אמורים להקל עלינו את הפיתוח האסינכרוני ולפשט את הקוד. יש הרבה חומר בנושא ,אבל ההרגשה שלי, אחרי הרבה מפגשים עם מפתחים היא, שהחומר לא פשוט ויש הרבה בלבול. בפוסט זה אנסה להסביר את הדברים קצת אחרת ולעזור לעשות סדר.

 

שאלה ראשונה שאנחנו צריכים לשאול את עצמנו היא

 

מה ההבדל בין Parallel Programming ל- Async Programming?

 

·       למה בדוט-נט 4 הדגש היה על Parallel ועל PLINQ ואילו בדוט-נט 4.5 שינו את המונחים ל- Async Programming?

 

תשובה:

 

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

 

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

 

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

 

מה זה תכנות מקבילי?

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

 

 

איך כל זה מתקשר לדוט-נט?

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

בדוט-נט 4-1 ה-API  התמקד בעיקר בתכנות מקבילי, שהמשמעות שלו הייתה ליצור Thread או Task שרץ

במקביל ל- Thread הראשי. עיקר הקושי שחווינו היה סנכרון בין שני התהליכים, ביטול תהליך, טיפול בשגיאות ועוד. במילים פשוטות הפיתוח המקבילי לא היה פשוט. בדוט-נט 4.5 פישטו את הדברים והוסיפו את היכולות לפרק משימה לתת משימות קטנות, כלומר תכנות אסינכרוני. בוא נסביר ע"י דוגמאות.

 

1.     תכנות סינכרוני:

image


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


image


 

2.     תיכנות אסינכרוני:

בתיכנות אסינכרוני אנחנו רוצים לפרק את המתודה BtnSyncClick לתהליכים קטנים כך שפעולות של UI  יוכלו להיכנס באמצע בזמנים המתים של החישוב של הערכים ע"י GetVal.  ע"י הוספת await בכל מקום בקוד שאנחנו מחכים וסימון המתודה ב-async, אנחנו מפרקים את המתודה לתת משימות. במילים פשוטות מילת המפתח await שוברת את המתודה ומפצלת אותה לשנים, לפני ה- await ואחרי, דומה מאוד ל- yield return.

 

image

בדוגמה שלפנינו אנחנו "שוברים" את המתודה ל-3 חלקים, Tasks.

image

אנחנו "שוברים" את הקוד לפני הקריאה למתודה GetValAsync, עד שהמתודה תחזיר ערך, בינתיים אפשר לבצע עוד פעולות, למשל של UI. ואנחנו "שוברים" את הקוד לפני המתודה task() כי גם היא איטית, היא תלויה בסיום שני המתודות GetValAsync.  בהסתכלות על ה- Stack Frame לאורך ציר הזמן ( התמונה להמחשה בלבד, המציאות יותר מורכבת ) אנחנו רואים שכאשר מגיעים למתודות שמסומנות ב- await השליטה חוזרת למתודה שקראה לה. במקרה שלנו יש await בתוך await, task() מחזיר שליטה ל-BtnAsyncClick והוא מחזיר שליטה ל- UI.

image

הסיבה שאנחנו רואים 4 פעמים את GetValAsync היא כיוון שאנחנו קוראים פעמיים  קוראים למתודה ו-await מפרק אותה לשנים. חשוב לציין שאין כאן מקבילות כי הכול רץ על UI Thread, פרקנו את התהליך ואנחנו מריצים אותו אסינכרוני.

 

אז למה צריך את await לפני task()?

 

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

נקודה נוספת, task() מוגדר כמתודה אסינכרונית , הוא מחזיר Task<string>. אנחנו רוצים את ה-Result של ה- task(), כלומר את ה- string ולא את ה-Task עצמו לכן מוסיפים await לפני ה-task().

 

נקודה חשובה לציון:

כאשר מתחילים להשתמש ב-await , המתודה חייבת להיות מסומנת ב-async  ויש סבירות גדולה שגם המתודה שקוראת לה תשתמש ב-await על מתודה זו ולכן גם היא תסומן ב- async וחוזר חלילה.

 

 

סיכום הדוגמא אסינכרונית:

דוט-נט 4.5 ע"י השימוש ב- aysnc & await מקל עלי את פירוק המתודות למספר משימות.  יש עוד יתרונות לשימוש ב- aysnc & await כמו ביטול משימה, אך זה שייך לפוסט אחר. 

3.      תכנות מקבלי:

נשפר את הקוד ע"י כך שנמקבל את העובדה לשני צירים מרכזים, ה- UI Thread ומתודה ה- Func.

 

image

 

השינוי המרכזי כאן הוא הוספה של Task.Run() שיוצר Task ומריץ אותו במקביל. ה- Stack Frame לא משתנה ( לא מדויק, אבל בשביל ההסבר זה מספיק טוב ) אך שמתי אותו על שני צירים למדל את שני ה- Threads שרצים במקביל.

 

image

 

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

 

אחד היתרונות של async & await שהוא מחזיר אותנו ל-Thread שממנו יצאנו , כלומר ההשמה ל- btnParallel.Content מתבצע על ה- UI Thread.

 

  

סיכום:

 

דוט-נט 4.0 התחיל את המהפכה עם המחלקה Task, לא עוד עבודה עם Thread, המפתח יגדיר את המשימות שיכולות לרוץ במקביל והדוט-נט ע"פ המחשב, מספר המעבדים יחליט אם להקצות לזה Thread חדש, בברירת המחדל 2 Thread ל-CPU. הדגש היה על מקבול תהליכים בצורה קלה יותר מעבודה עם Threads. המחלקה Task מכילה מספר מתודות שהיו חסרות ב-Thread ועושות את החיים הרבה יותר קלים, למשל:

·        Cancel()

·        ContinueWith()

·        ContinueWhenAll()

·        ContinueWhenAny()

 

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

 

דוט-נט 4.5 שם את הדגש בפירוק המשימות לתת משימות, בעיקר משימות שעושות תקשורת, IO ואין צורך למקבל אלא לפרק לשני חלקים. הפרוק מתבצע ע"י הוספת מילות המפתח async & await במקומות הנכונים, אין צורך ב- CallBack או events, הכתיבה היא כמו כתיבה סינכרונית.

 

 

Expert Days 2012

 

למי שהנושא מעניין אותו ורוצה להמשיך לחקור בנושא אני מזמין אתכם ליום עיון שאני עושה בכנס הגדול של השנה בחסות מיקרוסופט (http://www.e4d.co.il/Events/ExpertDays2012/Courses/Details/Async-and-Parallel-Programming ).

image

 

פידבקים יתקבלו בברכה.

Advertisements
 
3 תגובות

פורסם ע"י ב- אוקטובר 1, 2012 ב- Uncategorized

 

3 תגובות ל-“תכנות מקבילי או אסינכרוני?

  1. Rotem Bloom

    אוקטובר 2, 2012 at 4:06 am

    אחלה של מאמר, מסביר בצורה ברורה את ההבדל ועוד בעברית.
    כול הכבוד אדון ורדי.

     
  2. eyal.vardi

    אוקטובר 7, 2012 at 6:51 pm

    תודה, אני אבדוק.

     
  3. shirashirashira

    פברואר 18, 2016 at 12:59 pm

    שלום, תודה על המאמר,
    אני עובדת כרגע על ActionFilterAttribute בMVC. למיטב חיפושי אין אפשרות להשתמש בOnActionExecuting כASYNC. בכל זאת אני מעוניינת לעשות פעולות I/O בACTION הזה. האם נכון ואם כן כיצד לקרוא למטודה אסנכרונית מתוך הOnActionExecuting?

    אודה לעזרתך
    שירה

     

כתיבת תגובה

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

הלוגו של WordPress.com

אתה מגיב באמצעות חשבון WordPress.com שלך. לצאת מהמערכת / לשנות )

תמונת Twitter

אתה מגיב באמצעות חשבון Twitter שלך. לצאת מהמערכת / לשנות )

תמונת Facebook

אתה מגיב באמצעות חשבון Facebook שלך. לצאת מהמערכת / לשנות )

תמונת גוגל פלוס

אתה מגיב באמצעות חשבון Google+ שלך. לצאת מהמערכת / לשנות )

מתחבר ל-%s

 
%d בלוגרים אהבו את זה: