【问题标题】:C#/.NET Most performant way to call a method dynamicallyC#/.NET 动态调用方法的最高效方式
【发布时间】:2019-02-17 21:00:33
【问题描述】:

我们正在开发一个系统,它从 tcp/ip 流中读取命令,然后执行这些命令。命令由对对象的方法调用组成,该对象也由命令中的 id 标识。您可以将命令视为元素 id(我们要调用命令的寻址元素)和命令 id(寻址应该在元素上调用的方法)的信息。此外,我们还有一个问题,我们需要检查每个命令的某种权限以及应该如何执行该命令。 (是否应该在新的Thread等下启动)

这样一个命令调用的示例如下:

class Callee
{
    public void RegularCall(int command, parameters)
    {
        switch (command)
        {
            case 1: // Comand #1
                // Check if the permissions allow this command to be called.
                // Check if it should be outsourced to the ThreadPool and
                // call it accordingly. +Other Checks.
                // Finally execute command #1.
                break;
            case 2: // Comand #2
                // Check if the permissions allow that command to be called.
                // Check if it should be outsourced to the ThreadPool and
                // call it accordingly. +Other Checks.
                // Finally execute command #2.
                break;
            // Many more cases with various combinations of permissions and
            // Other flags.
        }
    }
}

在某个地方:

static Dictionary<int, Callee> callees = new Dictionary<int, Callee>();

static void CallMethod(int elementId, int commandId, parameters)
{
    callees[elementId].RegularCall(commandId, parameters);
}

但是,这种方法有些不雅:

  • 由于一遍又一遍地复制相同的代码,这可能容易出错。
  • 在某些情况下,很难看到存在哪些命令以及它们的标志是什么。
  • 命令方法充满了可能在方法之外进行的检查。

我的第一种方法是使用反射,看起来应该是这样的:

class Callee
{
    [Command(1)]
    [Permissions(0b00111000)]
    [UseThreadPool]
    public void SpeakingNameForCommand1(parameters)
    {
        // Code for command #1.
    }

    [Command(2)]
    [Permissions(0b00101011)]
    public void SpeakingNameForCommand2(parameters)
    {
        // Code for command #2.
    }

    // Again, many more commands.
}

这段代码一定是用一些反射重的代码初始化的:

  1. 查找所有可能代表一个元素的类。
  2. 查找所有具有命令属性等的方法。
  3. 将所有这些信息存储在字典中,包括对应的MethodInfo

接收命令的调用如下所示,其中CommandInfo 是一个包含调用所需的所有信息的类(MethodInfo,在ThreadPool 中运行,权限...):

static Dictionary<int, CommandInfo> commands = new Dictionary<int, CommandInfo>();

static void CallMethod(int elementId, int commandId)
{
    CommandInfo ci = commands[commandId];

    if (ci.Permissions != EVERYTHING_OK)
        throw ...;

    if (ci.UseThreadPool)
        ThreadPool.Queue...(delegate { ci.MethodInfo.Invoke(callees[elementId], params); });
    else
        ci.MethodInfo.Invoke(callees[elementId], params);
}

当我对此进行微基准测试时,对MethodInfo.Invoke 的调用比直接调用慢约 100 倍。 问题是:有没有一种更快的方法来调用这些“命令”方法,同时又不会失去定义这些命令应该如何调用的属性的优雅?

我还尝试从MethodInfo 派生一个委托。但是,这并没有很好地工作,因为我需要能够在 Callee 类的任何实例上调用该方法,并且不想为每个可能的 element * 命令的委托保留内存。 (会有很多元素。)

明确一点:MethodInfo.Invoke 比包含 switch/case 语句的函数调用慢 100 倍。这不包括遍历所有类、方法和属性的时间,因为这些信息已经准备好。

请不要告诉我网络等其他瓶颈。他们不是问题。他们没有理由在代码中的另一个位置使用慢速调用。谢谢。

【问题讨论】:

  • 如果您从网络流中读取数据,瓶颈很可能是您的网络,而不是MethodInfo.Invoke。优化后者可能不会对性能产生明显的影响。
  • 除了迈克尔的回答 - 所有的答案都可能归结为“不动态调用方法将是最快的”
  • Michael Liu 所说的,但如果可能的“正常”/关闭委托(包含引用的委托到目标对象实例)可能是一个问题......
  • 我真的忍不住了,你为什么还要争论伪代码。你怎么知道有什么不对?你甚至没有所有必要的信息来发表这种声明。
  • 嘿,看来 Scharle 让我免于写答案... :-)

标签: c# .net performance reflection


【解决方案1】:

您可以使用开放委托,它比MethodInfo.Invoke 快大约十倍。您可以像这样从MethodInfo 创建这样的delegate

delegate void OpenCommandCall(Callee element, parameters);

OpenCommandCall occDelegate = (OpenCommandCall)Delegate.CreateDelegate(typeof(OpenCommandCall), methodInfo));

然后你会这样称呼这个委托:

occDelegate.Invoke(callee, params);

其中callee 是您要对其调用方法的元素,methodInfo 是该方法的MethodInfoparameters 是各种其他参数的占位符。

【讨论】:

    【解决方案2】:

    也许你想试试ObjectMethodExecutor

    根据Hanselman

    如果您需要通过反射调用某个类型的方法并且该方法可能是异步的,我们有一个帮助器,我们在 ASP.NET Core 代码库中的任何地方都可以使用它,它高度优化且灵活,称为 ObjectMethodExecutor。

    团队在 MVC 中使用此代码来调用您的控制器方法。他们在 SignalR 中使用此代码来调用您的集线器方法。它处理异步和同步方法。它还处理自定义等待对象和 F# 异步工作流

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-27
      • 2013-10-09
      • 2011-02-22
      相关资源
      最近更新 更多