在多播委托上使用慢速 DynamicInvoke 的替代方案

2023-12-30

我在基类中有以下代码:

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)


如果 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<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 ();
        }
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在多播委托上使用慢速 DynamicInvoke 的替代方案 的相关文章

随机推荐

  • 当按钮位于列表中时,ListView 长按不起作用

    我有一个带有自定义列表适配器的 ListView 它有 OnItemClickListener 和 OnItemLongClickListner 它们曾经工作得很好 之后 我必须在列表项的布局中放置一个按钮 并且项目单击和长按侦听器停止工作
  • JSF 2 中的范围

    我有一个 CRUD 应用程序 数据在哪里获取 显示 编辑 存储回数据库 此外 页面之间存在顺序访问 第二页需要输入在第一页上输入的信息 依此类推 最佳范围是什么以及为什么 何时使用哪个范围 每个范围的生命周期是什么 另外 我找不到任何关于范
  • 如何以及为何设置 C# 构建机器? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我正在与一个小型 4 人 开发团队合作开发一个 C 项目 我建议设置一台构建机器来进行项目的夜间构建和测试 因为我知道这是一件好事 问
  • C# HttpWebRequest 显示 404,但可以在浏览器中访问网站

    我正在尝试使用 c 从网站下载 xml 文件 但在某些网址上收到 404 错误 这是有线的 因为它们仍然可以在浏览器中工作 其他网址仍然可以正常工作 HttpWebRequest request HttpWebRequest WebRequ
  • 未安装应用程序时,我的应用程序不会显示智能应用横幅

    我正在使用元标记在 iphone ipad 设备的应用程序横幅中显示我的应用程序 ios 当我使用 Iphone 进行测试时 如果安装了该应用程序 横幅将非常有效 如果未安装 则横幅不会显示 我还对 Facebook 等其他应用程序进行了相
  • 如何使用 Swift 在 iOS 中同时录制和播放音频?

    在 Objective C 中 同时录制和播放音频相当简单 互联网上有大量的示例代码 但我想在 Swift 中使用 Audio Unit Core Audio 同时录制和播放音频 关于使用 Swift 的帮助和示例代码非常少 我找不到任何可
  • Parboiled2 导致“加载类文件‘Prepender.class’时检测到缺少或无效的依赖项”

    因此 过去几周我一直在尝试使用 parboiled2 这可能是我一生中遇到过的最难添加到构建的依赖项 我当前的错误是编译sbt assembly error error missing or invalid dependency detec
  • 在 SQL Server 2005 中的视图上创建全文索引

    我在 SQL Server 2005 中的视图上创建全文索引时遇到问题 查看文档我没有发现问题 我收到的错误消息是 Id 不是强制执行全文搜索键的有效索引 全文搜索键必须是唯一的 不可为空的 不脱机的单列索引 是未在非确定性或不精确的非持久
  • 将浮点数显示为至少有 1 位小数的字符串

    我想将浮点数显示为字符串 同时确保至少显示一位小数 如果有更多小数 我希望显示这些 例如 1 应显示为 1 0 1 2345 应显示为 1 2345 有人可以帮我处理格式字符串吗 使用 ToString 0 并使用您想要的小数位数
  • 确定列表中当前指向的项目

    我正在处理列表 我已经能够确定列表中项目的第一个和最后一个位置 我在用getPostion并通过显示项目名称Label 我的表单中的三个按钮 ShowFirstItem ShowNextItem 不工作 和ShowLastItem在标签中显
  • Math.sqrt() 的更快替代方案

    有没有其他方法可以使用Math sqrt 获得未知值的平方根 例如 var random Math random 999 1 1 var sqrt Math sqrt random 我听说使用Math sqrt 获取数字的平方根是一个非常慢
  • 使用 Spring boot Rest 转换为 JSON 时,Java LocalDateTime 被转换为整数数组 [重复]

    这个问题在这里已经有答案了 我的代码如下 Data Document collection models public class Model Field value modelDt private LocalDateTime modelD
  • C++ 结构体排序错误

    我正在尝试对 C 中的自定义结构向量进行排序 struct Book public int H W V i 用一个简单的函子 class CompareHeight public int operator Book lhs Book rhs
  • 写入二进制 mangle 数据

    在我的应用程序中 我记录来自 ASIO 驱动程序的音频数据 void newData void buffer int length 其中 buffer 是缓冲区指针 length 是该缓冲区中的样本数 就我而言 我知道样本是 4 字节整数
  • Android Wear 捆绑通知和背景图像

    我想设置一个 Android Wear 应用程序 通过堆叠多个通知 然后在每个堆叠的通知上显示不同的背景图像和操作来扩展推送通知 http developer android com training wearables notificat
  • 寻找有关将 RETS 集成到 php 网站的示例或帮助

    我的任务是将 RETS I 集成到基于 php 的网站 我听说 phrets 是一个很好用的库 该网站位于共享托管平台 godaddy 上 我想我可以构建一个包含一些搜索字段的页面 并让它执行查询并将结果显示在第二页上 我读过的其他地方名字
  • com.android.tools.r8.errors.a:仅从 Android O 开始支持 MethodHandle.invoke 和 MethodHandle.invokeExact

    我正在开发 Android 本机应用程序 我已经为它们准备了一个图书馆项目包含这些应用程序的所有常用工具 我正在使用 jfrog artifactory 导入我的库 并且运行良好 现在我想用git 和子模块将我的图书馆放入我的项目中作为一个
  • JQuery 自动完成,使用 pHp json 中的数据填充

    我返回一个 JSON 编码的数组 echo json encode data 来自 php 我希望它能够填充 JQuery 自动完成的建议框 我正在使用这个 field autocomplete source SearchTest php
  • 如何从 T-SQL 获取当前实例名称

    如何使用 T SQL 脚本获取当前连接的 SQL Server 服务器和实例名称 刚刚找到答案 在这个问题 https stackoverflow com questions 129861 how can i query the name
  • 在多播委托上使用慢速 DynamicInvoke 的替代方案

    我在基类中有以下代码 public static void InvokeExternal Delegate d object param object sender if d null Check each invocation targe