2011-08-16 35 views
3

慢DynamicInvoke我有以下的基類的代碼:替代使用上muticast委託

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的類,並且非常關注跨線程,如Windows窗體控件。

從理論上講,這個片段工作得很好,沒有發生錯誤。但是DynamicInvoke速度非常慢,並不是說它是目前應用程序的瓶頸。

那麼,有沒有我的問題:有沒有什麼辦法可以加速這個小功能而不用功能上直接訂閱事件?

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

+1

你可以將你的輸入委託轉換爲一個已知的委託類型來調用它們嗎? – Gabe

+0

在某些情況下給EventHandler,但很遺憾沒有。 – Emiswelt

+0

請參閱此[問題](http://stackoverflow.com/questions/4751799/using-multicastdelegate-as-parameter-while-avoiding-dynamicinvoke)。底線:你可以使用'動態',這也是非常快的。 – nawfal

回答

7

如果dDelegate是一個已知的類型(即動作),你總是可以投給它,並直接調用它。

這樣說,如果你在.NET3.5上,你可以使用表達式樹來獲得一些優化。我的示例使用.NET4中的併發字典,但可以用普通字典和鎖取代。

這個想法如下:委託持有它調用的方法。對於被稱爲創建(使用表達式樹)的每個唯一方法,調用該特定方法的編譯委託。創建一個已編譯的委託代價很高,這就是緩存它很重要的原因,但是一旦創建了已編譯的委託,其速度與普通委託一樣快。

在我的機器上,3,000,000個調用花費了1秒的時間與編譯的委託和16秒的DynamicInvoke。

// 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<MethodInfo, CachedMethodDelegate> s_cachedMethods = 
      new ConcurrentDictionary<MethodInfo, CachedMethodDelegate>(); 

     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<CachedMethodDelegate>(
       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<object, EventArgs> 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我可能會錯誤地使用一些新的語言功能,但我沒有編譯使用.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<MethodInfo, CachedMethodDelegate> s_cachedMethods = 
      new Dictionary<MethodInfo, CachedMethodDelegate>(); 

     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(); 
     } 
    } 
} 
+0

謝謝你的幫助。 我在.Net 2.0上,但我會嘗試進入您的概念,也許切換到4.0。請給我一些時間來嘗試一下。 :) – Emiswelt

+0

只是FYI Linq表達式樹在.NET35中可用。上述技術在.NET2中也是可行的,但爭論更加困難。 – FuleSnabel

+0

我有點好奇,事情是如何發生的。無論如何.NET2樣本對你有幫助嗎?我有一些關於如何在需要時進一步提高性能的想法。 – FuleSnabel

2

如果你有一組已知類型的,你可以檢查他們的第一,只有恢復到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); 
    } 
} 
+0

這是一個不錯的主意,通過對FuleSnabel的解決方案進行修改,通過檢查特定的目標類型並直接調用調用目標上的方法來進一步提升程序的內部工作。 – Emiswelt

2

查看我的書架FastDelegate.Net。它在建設上做代碼生產,並且具有匹配的性能。