WCF 服务慢速调用取决于调用者

Posted

技术标签:

【中文标题】WCF 服务慢速调用取决于调用者【英文标题】:WCF service slow call depending on caller 【发布时间】:2021-08-26 07:24:15 【问题描述】:

我已经设置了一个本地 WCF 服务,它自托管在控制台应用程序中,使用 NetNamedPipeBinding 进行客户端访问。

为了调用服务,我引用了library.dll,其中我有以下方法:

public static string GetLevel(Point p)
   
    ChannelFactory<IService> pipeFactory = new ChannelFactory<IService>(new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/PTS_Service"));
    IService pipeProxy = pipeFactory.CreateChannel();
    string result = pipeProxy.GetLevel(p);
    ((IClientChannel)pipeProxy).Close();
    pipeFactory.Close();

GetLevel() 命令根据 Point(X,Y,Z) p 的 Z 坐标从存储在服务中的列表中返回一个字符串。

如果从上面的控制台应用程序调用该方法,则此方法有效并且总速度为 8 毫秒。

但是,当从另一个 app.exeplugin.dll(由外部程序加载)调用来自 library.dll 的相同方法时,时间会急剧增加。上面这5行代码我已经看完了:

consoleHost.exe:0 - 3 - 6 - 7 - 8 app.exe : 89 - 155 - 248 - 259 - 271 plugin.dll : 439 - 723 - 1210 - 1229 - 1245

时间不应该相同,不取决于谁拨打library.dll吗?

编辑

由于我已经取消了仅从正在运行的服务中检索字符串的所有方法,我相信问题在于 channelFactory 的第一次创建运行,同一应用程序/插件运行中的所有后续调用在时间上都是相等的。

我知道第一次调用速度较慢,但​​我发现这在新应用中大约是 30 毫秒,在我的插件中大约是 900 毫秒,我相信还有其他原因造成了这种情况。

我发现了一个类似延迟的问题: 解决方案是将LoaderOptimizationAttribute 设置为MultiDomain 的First WCF connection made in new AppDomain is very slow。是否有可能每次插件运行时都必须进行 JIT 编译而不是使用本机代码?

我尝试在 consoleHost.exe 的 main 上方添加此代码,但在插件运行时没有看到任何增益。这可能是因为两者之间的外部程序,有没有办法解决这个问题?假设我的插件可以在它想要访问服务时创建一个新的 Appdomain 并从这个新的 Appdomain 中调用我的library.dll 中的上述方法还是没有意义?

EDIT2

我使用 cmets 中建议的分析程序记录了 JIT 编译所花费的时间,这为 JIT 编译提供了 700 毫秒,插件的总执行时间为 800 毫秒。

我使用 ngen 预编译 library.dll 以创建本机映像 .ni.dll。我在进程资源管理器中看到该图像是由外部程序加载的,尽管插件没有时间增益?据我了解,插件仍然会 JIT 编译不应该是有原因的,还是我做错了什么?

在 VS 中调试时,我还注意到控制台和应用程序只加载一些程序集,插件在每次创建或修改插件实例时都会加载和卸载。我相信这是插件的工作方式,不应该解释第一次执行时间的差异吗?

【问题讨论】:

会不会是对dll的调用是对dll的第一次调用?在这种情况下,加载 dll 及其依赖项可能是第一次调用所需时间的一部分 dll是之前访问的(非服务方法)所以应该已经加载了吗?此外,为什么应用程序不会这么慢? 即时编译不能那样工作。它只编译被调用的函数,就在它们被调用之前。所以不是每个组件。将时间安排在您在 dll 内显示的代码周围。为了更准确地测量。 好的,我明白了。那么如何防止插件中的JIT编译呢?调用的每一行/函数的时间确实更大,如引用链接中所述。 我看到两个选项(如果这确实是问题所在): 1:在程序开始时调用所有重要功能一次或 2:使用 NGen.exe 预编译代码。 docs.microsoft.com/en-us/dotnet/framework/tools/… 【参考方案1】:

通信不应该依赖于调用者,而应该依赖于调用的方式。

最耗时的操作是创建频道。 如果代理一旦创建,那么下一次调用将以平均相似的速度完成。 (如果调用者从同一个地方使用服务:网络中的同一台机器。在你的情况下应该是相同的,而在你的情况下你使用本地主机)

一些性能提升也可以通过服务配置存档(SingleInstance 应该比 PerCall 更快)。

另外需要注意的一点是检查你的服务方法中可能存在的锁。当服务忙时,可能会发生某些服务客户端正在等待呼叫的情况。

如果服务调用不是异步调用,请尝试使其异步并使用它。

【讨论】:

我添加了 SingleInstance,性能可能略有提升。但是,我开始寻找服务以保持对多个列表甚至 SQLite 数据库的集中访问,并达到更接近 consoleHost.exe 测试速度的速度。有没有更好的方法来创建频道/代理?我一直在阅读有关内存缓存的信息,但我对此并不熟悉,只有在全局/不同进程中完成此操作才有意义。所有应用程序/服务始终在同一台机器上本地完成。 建立渠道别无选择。如果您使用 Ado.Db 访问,请检查您是否总是关闭您的数据库连接。如果是 EF,您必须在所有数据检索方法中使用异步调用,以防万一您遇到连接拉取问题。处理不当的数据检索将导致性能差距,并且在“激进”场景的情况下 - 连接超时我的建议:模拟数据访问。检查所有消费者的表现。如果它解决了问题,请更深入地模拟,直到找到有问题的地方。 我已将服务缩减为一个包含 4 个元素的预定义列表。所有数据库方法都被删除。我得到以下关于通道创建的测试结果:(C = 控制台主机;A = 应用程序;P = 插件)C1:21 毫秒 C2:6 毫秒 A1:29 毫秒 A2:6 毫秒 P1:784 毫秒 P2:1 毫秒 注意:我已经完成了第一次和第二次运行程序。控制台主机和应用程序的总时间更一致,但我看不出是什么导致插件第一次调用这么慢,是否还有更多安全配置要做? * 以上 5 行代码的总时间(毫秒) 我无法告诉你为什么插件第一次启动需要这么长时间。这个问题比您最初提出的问题要广泛得多;)但是原始问题已得到回答并且您已经检查过。创建通道后,平均访问速度大致相同。另一方面,插件的第一次数据访问时间小于 1 秒。这还不错(特别是对于插件,到目前为止,一切都在包装器、反射等上工作)【参考方案2】:

经过进一步调查:当进程启动时,外部程序通过 .ini 文件中的设置阻止共享加载的程序集/JIT 编译。幸运的是,这可以被禁用,因此插件中的共享也成为可能。

更改此设置后(将 .ini 中的 1 行改为“否”而不是“是”!)时间减少到 30 毫秒,每次下一次调用为 3 毫秒或更短。

【讨论】:

以上是关于WCF 服务慢速调用取决于调用者的主要内容,如果未能解决你的问题,请参考以下文章

在 WCF 中获取调用者的主机名

更改 WCF 服务以与 .NET Framework 和 .NET Core 调用者兼容

WCF 错误:调用者未通过服务的身份验证

自托管 WCF 服务和 basicHttpBinding:绑定不提供表示调用者的 Windows 标识

我可以使用调用者的信用来打数据库的 WCF 调用吗?

008. 阻塞&非阻塞同步&异步