Safe Value Pattern
בפוסט הזה: קוד קצר ולעניין של משתנה שקוראים אותו ומעדכנים אותו בסביבה שהיא Multi-Threaded.
כידוע, כדי להגן על המשתנה מפני corruption אנחנו צריכים נעילה (כן, אני יודע, יש גם את Interlock, אבל זה לא העניין פה, אל תטרידו אותי עם עובדות…)
אז במקום להצהיר בכל פעם על משתנה ועל האובייקט שנועל אותו, הנה Mini-Pattern שעושה את העבודה:
הגירסה הפשוטה
מנעול פשוט, עבודה עם lock:
namespace Pepperoni.Lib { public class SafeValue<T> { private readonly object locker = new object(); private T innerValue; public SafeValue() { } public SafeValue(T initialValue) { innerValue = initialValue; } public T Value { get { lock (locker) { return innerValue; } } set { lock (locker) { innerValue = value; } } } } }
אין הרבה מה לכתוב ב code-review, בסה"כ גם ב getter וגם ב setter נועלים אובייקט פנימי ורק אז מחזירים את הערך (ב getter) או מעדכנים את הערך (ב setter). קצר, פשוט, קריא, עובד (נראה לי שאני מאמץ את זה בראשי תיבות: קפק"ע! :-P).
אלטרנטיבה – הרבה קריאות, מעט עדכונים
אם אנחנו יודעים שיש הרבה יותר קריאות של הערך לעומת עדכונים של הערך, כלומר הרבה יותר שימושים ב getter מאשר ב setter, אז נוכל לייעל את העבודה אם נשתמש באובייקט ReaderWriterLockSlim (לקריאה נוספת – MSDN). הנה הקוד (עודכן לפי ההצעה של חן אגוזי):
using System.Threading; namespace Pepperoni.Lib { public class SafeValueSeldomWrites<T> { private T innerValue; private readonly ReaderWriterLockSlim locker = new ReaderWriterLockSlim(); public SafeValueSeldomWrites() { } public SafeValueSeldomWrites(T initialValue) { innerValue = initialValue; } public T Value { get { locker.EnterReadLock(); try { T result = innerValue; return result; } finally { locker.ExitReadLock(); } } set { locker.EnterWriteLock(); try { innerValue = value; } finally { locker.ExitWriteLock(); } } } } }
גם כאן הקוד יחסית פשוט וקריא, הנעילה מתבצעת עם ה ReaderWriterLockSlim שמוצהר כ private. רק לא לשכוח ש finally תמיד-תמיד מתבצע, גם אם יש return בתוך ה try שלו.
עוד כמה מילים
אם רוצים, אז אפשר לעשות כאן אבסטרקציה לשני המימושים (ע"י interface או base-class). אבל זה נראה לי מיותר, כי בד"כ יודעים כבר בזמן הפיתוח עצמו באיזה מימוש להשתמש.
עניין נוסף: במקרים מסויימים אפשר לעבוד עם Interlock, שמאפשר קריאה/כתיבה בסביבה שהיא Multi-Threaded, ללא שימוש ב lock הסטנדרטי. מומלץ לגגל או לקרוא ב MSDN.
אפשר להוריד מכאן את הקוד, למי ש copy-paste גדול עליו 🙂
תכנות נעים!
אחלה – שימושי.
רק הערה קטנה – סדרך כלל בעת השימוש ב ReadWriteLockSlim, כדאי לבצע try-finally על מנת להבטיח יציאה מהנעילה.
אמנם בשימוש בספציפי הזה הסבירות לנפילה בעת ההשמה לשדה היא די נמוכה, עדין כדאי לדבוק בזה על מנת להמנע מצרות בהמשך (למשל מישהו שיחליף את זה ב lambda שמחזירה ערך, או סתם העתקה של ה Pattern למקום אחר)
@חן אגוזי
חן, תודה על ההערה, שיניתי את הקוד. צ'ירס!