נעילת קטע קריטי בקוד אסינכרוני
-
שלום לכולם
היו היו ימים טובים ועליזים שבהם הייתי נועל קטעים קריטיים (רוצה לומר: קוד שאסור שיפעל במקביל מ2 טרידים וכדומה כגון שהוא עשוי להוסיף 2 שורות לדטה בייס כאשר הבדיקה מתבצעת בתחילת הקטע וכן הלאה) ב C# על ידי מילת המפתח lock ומכניס לתוכה אובייקט כלשהו בהתאם לדרישה.
אממה מאז שהעולם הפך אסינכרוני וכ 70% מהפונקציות הפכו לאסינכרוניות או בגלל מהותן, או בגלל שהן קוראות לפונקציות אסינכרוניות. הקסם של lock כבר לא קיים. אז מה האלטרנטיבות? ממה שראיתי יש סמפור סלים וכל מיני דברים שאינני מבין אותם כלל, וכאשר ניסיתי להעתיק קטעי קוד של הפתרונות הללו, נתקלתי בשגיאות משונות שלא הצלחתי להבין אותן בגלל שאינני מבין את הבסיס של סמפור סלים.
מודה ומתוודה שכבר שנה אני דוחה את הנעילות הללו, עד שקרתה תאונה כצפוי, ואני אנה אני בא. אז מי שיכול לכוון כאן לנושא הזה מאיפה להתחיל יבורך מפי עליון אמן. -
דוגמא פשוטה של שימוש בSemaphoreSlim
var ss = new SemaphoreSlim(1, 1); await ss.WaitAsync(); try { await DoSomeThingAsync(); } finally { ss.Release(); }
הסבר בקצרה:
- הבנאי מקבל שני פרמטרים:
- המספר ההתחלתי של בקשות לסמפור שיאושרו במקביל
- המספר המקסימלי של בקשות שיאושרו במקביל
בדוגמא למעלה מכיוון שהמספר מוגדר לאחד - רק בקשה אחת תאושר בכל פעם.
- המתודה
WaitAsync
ממתינה בצורה אסינכרונית עד אשר הCounter הפנימי שלSemaphoreSlim
יירד למספר נמוך יותר מהמספר המקסימלי שהוגדר, בסיום ההמתנה הCoutner הפנימי יעלה באחד ושורות הקוד הבאות יתבצעו. - המתודה
Release
מפחיתה את הCounter באחד, מה שמפנה מקום לבקשות הממתינות (חשוב לקרוא לRelease
בתוך הFinally
כך שהשחרור יתבצע גם במקרה של כשלון הקוד המופיע בTry
).
מספר הערות:
- חשוב שהאוביקט SemaphoreSlim יהיה משותף כך שלא יווצר מופע חדש בכל קריאה למתודה או יצירת מופע שמכיל אותו כשדה (כדאי לחשוב בכיוון שלSingleton או שדה סטטי).
- עבור EntityFramework השימוש בSemaphoreSlim אינו דרוש תודות לConcurrency Tokens/Timestamp שנוצרו על מנת לטפל בבעיה זו, להלן מדריך לOptimistic Concurrency.
- הבנאי מקבל שני פרמטרים: