203. Solving the problem with events - part 2

פעם שעברה התחלנו ליצור מחלקה שתייצג את הWeak-event שלנו.

מה שאנחנו רוצים לעשות זה שהReference של האובייקט שהמתודה שייכת אליו יוחזק בתור WeakReference, כדי שהוא לא יחשב בתור Reference בספירה של הGarbage Collector.

נוסיף למחלקה שלנו לכן Data Member מסוג זה:

1
2
3
4
5
6
7
8
9
private readonly WeakReference mTarget;
public object Target
{
get
{
return mTarget.Target;
}
}

הProperty ששמו Target הוא דומה לProperty שיש לDelegateים ששמו Target.

אנחנו נאתחל אותו בConstructor:

1
2
3
4
public WeakEventHandler(EventHandler<TEventArgs> handler)
{
mTarget = new WeakReference(handler.Target);
}

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

הדרך הפשוטה היא לשמור כMember את handler, אלא שזה לא יעבוד. למה? כי אז יש לנו Reference לhandler, שלו יש Reference לאובייקט שהמתודה שייכת אליו, ולכן שוב הGarbage Collector לא יאסוף אותנו.

מה שנוכל לעשות במקום זה איכשהו לשמור מידע המייצג איזו מתודה אנחנו מעוניינים להריץ.

זה אמור להזכיר לכם את הטיפוס MethodInfo המייצג Metadata של מתודה (טיפים מספר 146-150), ואכן לDelegate יש Property ששמו Method המחזיר את הMethodInfo של המתודה שאליה מצביע הDelegate.

אז נשמור אותו בצד:

1
private readonly MethodInfo mHandlerMethodInfo;

ונאתחל גם אותו בConstructor:

1
2
3
4
5
Public WeakEventHandler(EventHandler<TEventArgs> handler)
{
mTarget = new WeakReference(handler.Target);
mHandlerMethodInfo = handler.Method;
}

שימו לב שלמחלקה שלנו אין Reference ישיר לTarget של הDelegate, אלא רק WeakReference. (כי MethodInfo אינו מצביע לinstance של אובייקט, אלא מתאר מתודה באופן כללי)

עכשיו כל מה שנשאר לעשות זה לכתוב מה יקרה כאשר יקפוץ האירוע:

מה שהיינו רוצים לעשות זה פשוט לקרוא לInvoke של המתודה:

1
2
3
4
5
public void Invoke(object sender, TEventArgs e)
{
mHandlerMethodInfo.Invoke(mTarget.Target,
new object[] {sender, e});
}

אלא שאנחנו צריכים לבדוק שהאובייקט שלנו עדיין בחיים.

כשרואים שלWeakReference יש Property בשם IsAlive, מפתה לכתוב משהו כזה:

1
2
3
4
5
6
7
8
public void Invoke(objectsender, TEventArgs e)
{
if (mTarget.IsAlive)
{
mHandlerMethodInfo.Invoke(mTarget.Target,
new object[] {sender, e});
}
}

IsAlive הוא Property המחזיר האם האובייקט שהWeakReference מצביע אליו עדיין בחיים, או שנאסף ע"י הGarbage Collector.

יש בעיה עם הכתיבה הזאת כיוון שהיא גורמת לRace Condition עם הGarbage Collector – יכול להיות שבתנאי הTarget עדיין היה בחיים, אבל שורה אחרי זה, כשאנחנו ניגשים לProperty ששמו Target, הReference כבר נאסף ע"י הGarbage Collector.

לכן הדרך היותר נכונה לכתוב זאת היא כך:

1
2
3
4
5
6
7
8
9
10
public void Invoke(object sender, TEventArgs e)
{
object target = mTarget.Target;
if (target != null)
{
mHandlerMethodInfo.Invoke(target,
new object[] {sender, e});
}
}

אנחנו מכניסים את הReference שאליו מצביע הWeakReference למשתנה לוקאלי. כעת יש אליו Reference אמיתי (לא WeakReference) ולכן הGarbage Collector לא ינסה לאסוף אותו.

אחרי זה אנחנו קוראים לInvoke של mHandlerMethodInfo, כלומר מקפיצים את הפונקציה שנרשמו איתה לEvent שלנו.

לבסוף מאחר וtarget הוא משתנה לוקאלי, הוא נאסף ע"י הGarbage Collector ביציאה מהפונקציה, ולכן אחרי הקפצת האירוע, מספר הReferenceים שיש לאובייקט שאליו שייכת המתודה חוזר להיות מה שהיה לפני הקריאה למתודה.

זהו מימוש די פשוט לWeak-event. כעת אם נקרא למתודה, באמת תקרא המתודה איתה נרשמנו, לדוגמה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Subject
{
public eventEventHandler<EventArgs> EventRaised;
public void Raise()
{
if (EventRaised != null)
{
EventRaised(this,EventArgs.Empty);
}
}
}
public class Observer
{
public Observer(Subject source)
{
source.EventRaised += new WeakEventHandler<EventArgs>(OnEventRaised);
}
private void OnEventRaised(object sender,EventArgs e)
{
Console.WriteLine(DateTime.Now);
}
}

אז קריאה כזאת:

1
2
3
Subject subject = new Subject();
Observer observer = new Observer(subject);
subject.Raise(); // Writes the current time

תדפיס את התאריך הנוכחי.

הכל נראה אחלה, אז מה הבעיה?

בעיה אחת היא העניין של ביצועים – ידוע הרי שקריאה לInvoke של MethodInfo היא הרבה יותר איטית מקריאה לDelegate.

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

בהמשך נדבר על פתרונות אפשריים לבעיות אלה,

המשך יום מלא אירועים חסרי דליפות טוב.

שתף