另一种使用上muticast委托缓慢DynamicInvoke [英] alternative for using slow DynamicInvoke on muticast delegate

查看:328
本文介绍了另一种使用上muticast委托缓慢DynamicInvoke的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个基类下面的一段代码:

I have the following piece of code in a base-class:

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窗体控件。

This code sample is responsible for invoking an event, represented as multicast delegate, where the invocation targets include small classes which do not care about cross-threading, but also classes which implement ISynchronizeInvoke and care a lot about cross-threading, like Windows Forms Controls.

在理论上,这个片段工程相当不错,没有出现错误。但 DynamicInvoke 是难以置信的慢,不是说这是应用程序的当前瓶颈。

In theory, this snippet works pretty fine, no errors occur. But the DynamicInvoke is incredibly slow, not to say it's the current bottleneck of the application.

那么,还有我的问题:有没有什么办法可以加快这个小功能,而不会破坏功能订阅直接在事件

So, there goes my question: Is there any way to speed up this little function without breaking the functionally to subscribe to the event directly?

所有事件/代表的签名是(对象发件人,EventArgs参数)

The signature of all events/delegates is (object sender, EventArgs param)

推荐答案

如果dDelegate是一个已知的类型(即动作)你总是可以投给它,并直接调用它。

If dDelegate is a known type (ie Action) you could always cast to it and call it directly.

随着中说如果你是在.NET3.5可以使用表达式树来获得最优化的公平位。我的示例使用.NET4并行字典但那是无可替代与正常字典和锁

With that said if you are on .NET3.5 you can use Expression trees to get a fair bit of optimization. My example uses the concurrent dictionary in .NET4 but that's replacable with a normal dictionary and a lock.

这个想法是如下:委托持有它调用哪个方法。对于每一个独特的方法被调用创建(使用表达式树)编译的委托,调用特定的方法。创建编译的委托是昂贵的,这就是为什么它的缓存是重要的,但一旦被创建编译委托是一样快,一个正常的委托。

The idea is as following: The delegate holds which method it's calling to. For each unique method that is called I create (using Expression trees) a compiled delegate that calls that specific method. Creating a compiled delegate is expensive that's why it's important to cache it but once created the compiled delegate is as fast as a normal delegate.

在我的机器3000000电话花了1秒编译代表和16秒,DynamicInvoke。

On my machine 3,000,000 calls took 1 sec with the compiled delegate and 16 sec with 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运行时)的例子。

As OP uses .NET2 I added an example that should be compatible with .NET2 runtime (as I use VS2010 I might use some new language features by mistake but I did compile using .NET2 runtime).

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

这篇关于另一种使用上muticast委托缓慢DynamicInvoke的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆