204. Solving the problem with events - part 3

פעם שעברה ראינו איך אפשר לממש Weak event בסיסי בעזרת שימוש בWeakReference והפעלת המתודה בעזרת הפעלת Invoke של MethodInfo.

אמרתי שיש עם בעיה זו שתי בעיות:

  1. אמנם כרגע לא דולף האובייקט שהDelegate מצביע אליו, אבל דולפת המחלקה WeakEventHandler.
  2. הפעלת מתודה בעזרת Invoke היא יותר איטית מהפעלה ישירה.

כרגע נתמקד בפתרון של הבעיה השנייה, מאחר ודי קל לפתור את הבעיה הראשונה.

ראשית נהפוך את המחלקה WeakEventHandlerלאבסטרקטית. נוסיף לה מתודה בשם InnerInvoke:

1
protected abstract void InnerInvoke(object target, object sender, TEventArgs eventArgs);

ונשנה את המימוש של הפונקציה Invoke כך שיקרא לפונקציה זו כאשר הReference עדיין בחיים:

1
2
3
4
5
6
7
8
9
public void Invoke(objectsender, TEventArgs eventArgs)
{
object target = mTarget.Target;
if (target != null)
{
InnerInvoke(target, sender, eventArgs);
}
}

בנוסף נהפוך את הMemberים לprotected.

כעת את המימוש שראינו אתמול, אפשר לכתוב ככה:

1
2
3
4
5
6
7
8
9
10
11
12
public class ReflectionWeakEventHandler<TEventArgs>: WeakEventHandler<TEventArgs>
where TEventArgs : EventArgs
{
public ReflectionWeakEventHandler(EventHandler<TEventArgs> handler) : base(handler)
{
}
protected override void InnerInvoke(object target,object sender, TEventArgs eventArgs)
{
mHandlerMethodInfo.Invoke(target,new object[] {sender, eventArgs});
}
}

כפי שציינתי בעבר (טיפים מספר 148,151,181), קריאה לפונקציה Invoke היא קריאה יחסית איטית.

הטריק שאפשר לעשות הוא ליצור מתודה בזמן ריצה עם החתימה של InnerInvoke, שInnerInvoke תקרא לה בלי Dynamic Binding:

ניצור מחלקה כזאת:

1
2
3
4
public class RuntimeWeakEventHandler<TEventArgs> : WeakEventHandler<TEventArgs>
where TEventArgs : EventArgs
{
}

בה יהיה Delegate עם החתימה של InnerInvoke:

1
private delegate void RaiseEventHandler(object target, object sender, TEventArgs eventArgs);

ונחזיק Member כזה:

1
private readonly RaiseEventHandler mRaisingHandler;

שנקרא לו בפונקציה InnerInvoke:

1
2
3
4
protected override void InnerInvoke(object target, object sender, TEventArgs eventArgs)
{
mRaisingHandler(target, sender, eventArgs);
}

כעת נותר לאתחל את mRaisingHandler בConstructor:

1
2
3
4
5
public RuntimeWeakEventHandler(EventHandler<TEventArgs> handler)
: base(handler)
{
mRaisingHandler = CreateRaiseEventHandler();
}

הפונקציה שמאתחלת את הDelegate נראית כך:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private RaiseEventHandler CreateRaiseEventHandler()
{
ParameterExpression target = Expression.Parameter(typeof(object),"target");
ParameterExpression sender = Expression.Parameter(typeof(object),"sender");
ParameterExpression e = Expression.Parameter(typeof(TEventArgs),"e");
UnaryExpressionconvertedTarget =
Expression.Convert(target, mHandlerMethodInfo.DeclaringType);
MethodCallExpression eventRaise =
Expression.Call(convertedTarget,
mHandlerMethodInfo,
sender,
e);
Expression<RaiseEventHandler> raiserLambda =
Expression.Lambda<RaiseEventHandler>(eventRaise,
target,
sender,
e);
return raiserLambda.Compile();
}

עושים פה הסבה של target לטיפוס האמיתי שלו (ראו גם טיפ מספר 161) ואז קוראים לפונקציה mHandlerMethodInfo עם הפרמטרים sender וe.

אחר כך הופכים את כל זה לLambda Expression ואז מחזירים את התוצאה המקומפלת. ראו גם טיפים על Expression Trees: 173-181.

כעת הקפצה של RuntimeWeakEventHandlerכזה יותר מהירה מהקפצה של ReflectionWeakEventHandler.

עם זאת, יצירה של RuntimeWeakEventHandler יותר איטית, כיוון שאנחנו יוצרים כל פעם מתודה חדשה ע"י הפונקציה Compile.

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

1
2
private static readonly Dictionary<MethodInfo,RaiseEventHandler> mMethodInfoToRaisingHandler =
new Dictionary<MethodInfo, RaiseEventHandler>();

במקום לקרוא לפונקציה CreateRaiseEventHandler נקרא לפונקציה אחרת:

1
2
3
4
public RuntimeWeakEventHandler(EventHandler<TEventArgs> handler) : base(handler)
{
mRaisingHandler = GetRaiser();
}

ותוכן הפונקציה GetRaiser הוא פשוט:

1
2
3
4
5
6
7
8
9
10
11
12
private RaiseEventHandler GetRaiser()
{
RaiseEventHandler raiseEventHandler;
if(!mMethodInfoToRaisingHandler.TryGetValue(mHandlerMethodInfo, out raiseEventHandler))
{
raiseEventHandler = CreateRaiseEventHandler();
mMethodInfoToRaisingHandler[mHandlerMethodInfo] = raiseEventHandler;
}
return raiseEventHandler;
}

למי שמעוניין במספרים:

בהקפצה של אירוע שרשומים אליו 100 רשומים (בלי תוכן בפונקציה הנרשמת):

הקפצה ישירה לוקחת בממוצע 0.000805100000000011 מילישניות

הקפצה שלReflectionWeakEventHandler לוקחת בממוצע 0.3630364 מילישניות

הקפצה של RuntimeWeakEventHandler לוקחת בממוצע 0.00414629999999997 מילישניות

שימו לב שהאירוע שהקפצתי הם ריקים, כלומר כל מה שמדדנו כאן הוא מהו הOverhead של שימוש בWeakEventHandler.

המשך יום מלא אירועים לא כל כך חלשים

שתף