在muticast委托上使用慢速DynamicInvoke的替代方法

我在基类中有以下代码:

public static void InvokeExternal(Delegate d, object param, object sender) { if (d != null) { //Check each invocation target foreach (Delegate dDelgate in d.GetInvocationList()) { if (dDelgate.Target != null && dDelgate.Target is System.ComponentModel.ISynchronizeInvoke && ((System.ComponentModel.ISynchronizeInvoke)(dDelgate.Target)).InvokeRequired) { //If target is ISynchronizeInvoke and Invoke is required, invoke via ISynchronizeInvoke ((System.ComponentModel.ISynchronizeInvoke)(dDelgate.Target)).Invoke(dDelgate, new object[] { sender, param }); } else { //Else invoke dynamically dDelgate.DynamicInvoke(sender, param); } } } } 

此代码示例负责调用一个事件,表示为多播委托,其中调用目标包括不关心跨线程的ISynchronizeInvoke以及实现ISynchronizeInvoke并关注跨线程的类,如Windows窗体控件。

理论上,这个片段工作得很好,没有错误发生。 但DynamicInvoke非常慢,并不是说它是应用程序的当前瓶颈。

所以,我的问题是:有没有办法加速这个小function而不破坏function直接订阅事件?

所有事件/代理的签名是(object sender, EventArgs param)

如果dDelegate是一个已知类型(即Action),你总是可以转换它并直接调用它。

有了这个说,如果你在.NET3.5上,你可以使用表达式树来获得一些优化。 我的示例使用.NET4中的并发字典,但可以使用普通字典和锁替换。

这个想法如下:委托持有它调用的方法。 对于每个被调用的唯一方法,我创建(使用表达式树)一个调用该特定方法的已编译委托。 创建一个已编译的委托是很昂贵的,这就是为什么缓存它很重要但是一旦创建了已编译的委托就像普通委托一样快。

在我的机器上,使用已编译的委托进行3,000,000次调用需要1秒,使用DynamicInvoke需要16秒。

 // Comment this line to use DynamicInvoke instead as a comparison #define USE_FAST_INVOKE namespace DynInvoke { using System; using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; static class Program { delegate void CachedMethodDelegate (object instance, object sender, EventArgs param); readonly static ConcurrentDictionary s_cachedMethods = new ConcurrentDictionary (); public static void InvokeExternal(Delegate d, object sender, EventArgs param) { if (d != null) { //Check each invocation target foreach (var dDelgate in d.GetInvocationList()) { if ( dDelgate.Target != null && dDelgate.Target is System.ComponentModel.ISynchronizeInvoke && ((System.ComponentModel.ISynchronizeInvoke)(dDelgate.Target)).InvokeRequired ) { //If target is ISynchronizeInvoke and Invoke is required, invoke via ISynchronizeInvoke ((System.ComponentModel.ISynchronizeInvoke)(dDelgate.Target)).Invoke(dDelgate, new object[] { sender, param }); } else { #if USE_FAST_INVOKE var methodInfo = dDelgate.Method; var del = s_cachedMethods.GetOrAdd (methodInfo, CreateDelegate); del (dDelgate.Target, sender, param); #else dDelgate.DynamicInvoke (sender, param); #endif } } } } static CachedMethodDelegate CreateDelegate (MethodInfo methodInfo) { var instance = Expression.Parameter (typeof (object), "instance"); var sender = Expression.Parameter (typeof (object), "sender"); var parameter = Expression.Parameter (typeof (EventArgs), "parameter"); var lambda = Expression.Lambda( Expression.Call ( Expression.Convert (instance, methodInfo.DeclaringType), methodInfo, sender, parameter ), instance, sender, parameter ); return lambda.Compile (); } class MyEventListener { public int Count; public void Receive (object sender, EventArgs param) { ++Count; } } class MyEventSource { public event Action AnEvent; public void InvokeAnEvent (EventArgs arg2) { InvokeExternal (AnEvent, this, arg2); } } static void Main(string[] args) { var eventListener = new MyEventListener (); var eventSource = new MyEventSource (); eventSource.AnEvent += eventListener.Receive; var eventArgs = new EventArgs (); eventSource.InvokeAnEvent (eventArgs); const int Count = 3000000; var then = DateTime.Now; for (var iter = 0; iter < Count; ++iter) { eventSource.InvokeAnEvent (eventArgs); } var diff = DateTime.Now - then; Console.WriteLine ( "{0} calls took {1:0.00} seconds (listener received {2} calls)", Count, diff.TotalSeconds, eventListener.Count ); Console.ReadKey (); } } } 

编辑:由于OP使用.NET2,我添加了一个应该与.NET2运行时兼容的示例(因为我使用VS2010我可能会错误地使用一些新的语言function,但我确实使用.NET2运行时编译)。

 // Comment this line to use DynamicInvoke instead as a comparison #define USE_FASTER_INVOKE namespace DynInvoke { using System; using System.Globalization; using System.Reflection.Emit; using System.Collections.Generic; using System.ComponentModel; using System.Reflection; static class FasterInvoke { delegate void CachedMethodDelegate (object instance, object sender, EventArgs param); readonly static Dictionary s_cachedMethods = new Dictionary (); public static void InvokeExternal (Delegate d, object sender, EventArgs param) { if (d != null) { Delegate[] invocationList = d.GetInvocationList (); foreach (Delegate subDelegate in invocationList) { object target = subDelegate.Target; if ( target != null && target is ISynchronizeInvoke && ((ISynchronizeInvoke)target).InvokeRequired ) { ((ISynchronizeInvoke)target).Invoke (subDelegate, new[] { sender, param }); } else { #if USE_FASTER_INVOKE MethodInfo methodInfo = subDelegate.Method; CachedMethodDelegate cachedMethodDelegate; bool result; lock (s_cachedMethods) { result = s_cachedMethods.TryGetValue (methodInfo, out cachedMethodDelegate); } if (!result) { cachedMethodDelegate = CreateDelegate (methodInfo); lock (s_cachedMethods) { s_cachedMethods[methodInfo] = cachedMethodDelegate; } } cachedMethodDelegate (target, sender, param); #else subDelegate.DynamicInvoke (sender, param); #endif } } } } static CachedMethodDelegate CreateDelegate (MethodInfo methodInfo) { if (!methodInfo.DeclaringType.IsClass) { throw CreateArgumentExceptionForMethodInfo ( methodInfo, "Declaring type must be class for method: {0}.{1}" ); } if (methodInfo.ReturnType != typeof (void)) { throw CreateArgumentExceptionForMethodInfo ( methodInfo, "Method must return void: {0}.{1}" ); } ParameterInfo[] parameters = methodInfo.GetParameters (); if (parameters.Length != 2) { throw CreateArgumentExceptionForMethodInfo ( methodInfo, "Method must have exactly two parameters: {0}.{1}" ); } if (parameters[0].ParameterType != typeof (object)) { throw CreateArgumentExceptionForMethodInfo ( methodInfo, "Method first parameter must be of type object: {0}.{1}" ); } Type secondParameterType = parameters[1].ParameterType; if (!typeof (EventArgs).IsAssignableFrom (secondParameterType)) { throw CreateArgumentExceptionForMethodInfo ( methodInfo, "Method second parameter must assignable to a variable of type EventArgs: {0}.{1}" ); } // Below is equivalent to a method like this (if this was expressible in C#): // void Invoke (object instance, object sender, EventArgs args) // { // ((<%=methodInfo.DeclaringType%>)instance).<%=methodInfo.Name%> ( // sender, // (<%=secondParameterType%>)args // ); // } DynamicMethod dynamicMethod = new DynamicMethod ( String.Format ( CultureInfo.InvariantCulture, "Run_{0}_{1}", methodInfo.DeclaringType.Name, methodInfo.Name ), null, new[] { typeof (object), typeof (object), typeof (EventArgs) }, true ); ILGenerator ilGenerator = dynamicMethod.GetILGenerator (); ilGenerator.Emit (OpCodes.Ldarg_0); ilGenerator.Emit (OpCodes.Castclass, methodInfo.DeclaringType); ilGenerator.Emit (OpCodes.Ldarg_1); ilGenerator.Emit (OpCodes.Ldarg_2); ilGenerator.Emit (OpCodes.Isinst, secondParameterType); if (methodInfo.IsVirtual) { ilGenerator.EmitCall (OpCodes.Callvirt, methodInfo, null); } else { ilGenerator.EmitCall (OpCodes.Call, methodInfo, null); } ilGenerator.Emit (OpCodes.Ret); return (CachedMethodDelegate)dynamicMethod.CreateDelegate (typeof (CachedMethodDelegate)); } static Exception CreateArgumentExceptionForMethodInfo ( MethodInfo methodInfo, string message ) { return new ArgumentException ( String.Format ( CultureInfo.InvariantCulture, message, methodInfo.DeclaringType.FullName, methodInfo.Name ), "methodInfo" ); } } static class Program { class MyEventArgs : EventArgs { } class MyEventListener { public int Count; public void Receive (object sender, MyEventArgs param) { ++Count; } } delegate void MyEventHandler (object sender, MyEventArgs args); class MyEventSource { public event MyEventHandler AnEvent; public void InvokeAnEvent (MyEventArgs arg2) { FasterInvoke.InvokeExternal (AnEvent, this, arg2); } } static void Main (string[] args) { MyEventListener eventListener = new MyEventListener (); MyEventSource eventSource = new MyEventSource (); eventSource.AnEvent += eventListener.Receive; MyEventArgs eventArgs = new MyEventArgs (); eventSource.InvokeAnEvent (eventArgs); const int count = 5000000; DateTime then = DateTime.Now; for (int iter = 0; iter < count; ++iter) { eventSource.InvokeAnEvent (eventArgs); } TimeSpan diff = DateTime.Now - then; Console.WriteLine ( "{0} calls took {1:0.00} seconds (listener received {2} calls)", count, diff.TotalSeconds, eventListener.Count ); Console.ReadKey (); } } } 

如果您有一组已知类型,则可以先检查它们,如果在编译时不知道类型,则只返回DynamicInvoke

 // delegate is most likely to be EventHandler var e1 = dDelegate as EventHandler; if (e1 != null) e1(sender, param); else { // might be DelegateType2 var d2 = dDelegate as DelegateType2; if (d2 != null) d2(sender, param); else { // try DelegateType3 var d3 = dDelegate as DelegateType3; if (d3 != null) d3(sender, param); else // last resort dDelgate.DynamicInvoke(sender, param); } } 

看看我的图书馆FastDelegate.Net 。 它在构造上具有codegen,并且具有匹配的性能。