הפעלת פונקציה אסינכרונית ללא await
-
אני מעוניין להפעיל פונקציות בשיטת "שגר ושכח" מבלי לעכב שום דבר במערכת, ממה שאני קורא בתיעודים של מייקרוסופט זה לא מומלץ מבחינת ארכיטקטורה כי "לרוב" אתה רוצה שהדברים ייעשו בזה אחר זה וכו'. אבל לא קראתי שיש סיכון ממשי שהפונקציה עלולה לא לסיים את הביצוע שלה בגלל הריגת טרידים וכדומה. כלומר זוהי פעולה שמותרת על פי חוק.
האם אני צודק במה שאמרתי? למשל אם אני עובד עם ASP.NET קיבלנו במסורת שאחרי שהוא החזיר ריספונס "הכל מת", אבל אם פונקציה אסינכרונית ששיגרתי קודם עדיין לא סיימה את עבודתה? היא תמות יחד עם פלישתים? או שהוא ממתין עד שכל הפונקציות האסינכרוניות יסיימו את עבודתן ואז הורג את הפלישתים בלבד. מזיכרון מעומעם קראתי פעם שהמשגר פונקציה אסינכרונית ללא אווייט דומה למשחק ברולטה רוסית בעצימת עיניים. בכה הדוד בוב ואמר לא די לו למהמר שהוא משחק ברולטה אלא שעוצם את עיניו. -
@ארכיטקט מדובר בנושא שיש הרבה מקום להרחיב בו, בל"נ אנסה לקצר, אם יש לך השגות או שאלות אשמח להרחיב.
הדרך המומלצת להריץ משימות רקע בASP.NET בפרט ובNET. בכלל, היא שימוש בHosted Service מדובר בService הרשום כSingleton החי בפני עצמו תחת אותו Host המריץ את האפליקציה.Host
התהליך המארח - מופקד על ההרצה וניהול חיי האפליקציות המתארחות, בנוסף תפקידו לטפל בכל הפרוצדורות הגנריות כדוגמת Logging וDI (מה שמאפשר שיתוף Services בין האפליקציות המתארחות עקב יצירתו של Root ServiceProvider בודד), והוא מוגדר ברובו באופן זהה ללא קשר לסוג האפליקציה המתארחת.צורת המימוש - Producer–Consumer:
האפליקציה והHosted Service יחלקו (באמצעות Serivce משותף) תור של משימות המחזירות Task\ValueTask (לדוגמה<<Channel<Func<CancellationToken, ValueTask
) לחלופין ניתן ליצור מחלקה ייחודית אשר תייצג משימת-רקע בודדת, המחלקה תכיל את המשימה עצמה כשדה, ובנוסף תכיל גם GUID כך שיהיה ניתן להחזירו ללקוח להמשך מעקב ולצורך מתן אפשרות לביטול (באמצעות CancellationToken ייחודי שיהווה שדה נוסף במחלקה ההיפותטית הנ"ל)לסיכום:
- האפליקציה תשחק את תפקיד הProducer - תפקידה יהיה להוסיף את המשימות לתור באופן סינכרוני לחלוטין
- הHosted Service את תפקיד הConsumer - תפקידו יהיה להריץ את המשימות הנכנסות לתור (אחת אחרי השניה במקרה של Channel או במקביל עם שימוש בActionBlock) וטיפול בשגיאות
-
@dovid אמר בהפעלת פונקציה אסינכרונית ללא await:
רק תרשה לי לשחרר את הבקשה
שחרור הבקשה מתבצע (בלי לקחת בחשבון Filters וMiddlewares) בעת הרצת הreturn (גם בעת כתיבה ישירה לBody) לאחר מכן הController ושאר הServices שרשומים כScope יושמדו (תיאורטית ניתן להזריק את IServiceScopeFactory (שהוא Singleton) ולבקש את הServices הדרושים גם אחרי שהבקשה הושלמה, אם כי הבעיה הגדולה של טיפול בשגיאות עדיין קיימת)
-
@רפאל אמר בהפעלת פונקציה אסינכרונית ללא await:
הדרך המומלצת להריץ משימות רקע בASP.NET בפרט ובNET. בכלל, היא שימוש בHosted Service
מכיר את זה, יש גם כלים לניהול ג'ובים וכדומה. זה לא התרחיש.
@dovid אמר בהפעלת פונקציה אסינכרונית ללא await:
@רפאל לא אכפת לי "להמתין", רק תרשה לי לשחרר את הבקשה עם תשובה מוגמרת ("OK אנחנו תיכף עושים זאת").
זה בדיוק התרחיש, אני רוצה שהמערכת תעשה "עוד כמה סידורים קטנים" אחרי שהתקבלה תשובה, בשביל זה אני חייב להרים מפלצות? ולפבלש אותם עם כל שינוי?
הרי הארכיטקטורה לפי דבריך תהיה רישום בקובץ טקסט שיש "משימה לעשות" ואז הסרביס השני יבצע את זה. לא הרעיון הכי נקי בעולם.... אבל אם זה מה יש מי אני שתאווכח. -
@רפאל אמר בהפעלת פונקציה אסינכרונית ללא await:
@ארכיטקט לא ברור לי למה אתה קורה "התרחיש". הפתרון שהצעתי הוא הפתרון היעיל ביותר שקיים היום עבור פעולות אסינכרוניות קצרות וארוכות כאחד.
הדוגמא ש @dovid נתן היא קלאסית, יוזר לוחץ על לחצן "שלח מיילים לכל המדינה", או "שלח חיוב לעשרה מיליארד כרטיסי אשראי", וכל כיו"ב.
הוא מקבל הודעה OK 200 "תודה לך, אנחנו מטפלים בבקשה שלך, אתה יכול להמשיך בענייניך". מתוך הנחה שהפרוסס ימשיך, איתו או בלעדיו. עד שיסיים את שלו.
במקום זה עלי להיות מוטרד מכך שמא יסגור את הטאב ויהרוג את הריקווסט וכן הלאה. ועל מנת שלא להיות מוטרד מכך, עלי לרשום בקובץ טקסט בדיסק הקשיח או בכל מקום פרסיסטנטיאלי, לבצע משימה נפרדת וכן הלאה. מקווה שאני מובן מספיק טוב. -
@ארכיטקט אמר בהפעלת פונקציה אסינכרונית ללא await:
עלי לרשום בקובץ טקסט בדיסק הקשיח... לבצע משימה נפרדת וכן הלאה.
פספסתי. אני חושד שיש לנו חוסר הבנה.
אופן הוספת המשימות לתור זהה ואינו תלוי כלל בטיב המשימה:
[HttpPost] public Guid Post(Model model) { return _queue.Enqueue((ct, sp) => sp.GetService<ISomeService>().DoAsync(model, ct)); }
-
@ארכיטקט ממש לא, גם לא Thread בפני עצמו.
HostedService זה Service מסוג סינגלטון רגיל אך בעל Lifetime משלו (נוצר בתחילת הריצה של האפליקציה [בניגוד לServices רגילים הנוצרים לפי הצורך], ומושמד בסיום הריצה), אין בו שום קסם אולם הוא מהווה פלטפורמה נחוצה להרצת משימות ארוכות או מתוזמנות.