65.9K
CodeProject 正在变化。 阅读更多。
Home

构建 Silverlight 企业应用程序时的冒险 - 第 34 部分

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2010年11月18日

CPOL

6分钟阅读

viewsIcon

7419

分析 Silverlight 业务应用程序的性能,包括多个服务层

在本篇文章中,我们将深入分析 Silverlight 业务应用程序的性能,包括多个服务层,并会遇到一个使用 ADO.NET Entity Framework 的常见问题,以及一个并不那么明显的解决方案。

故事

目前,我们正在努力尽快向客户交付成果。然而,性能问题是不断出现的问题之一。许多开发人员会给出的通用回应是,在堆栈中使用的不同技术上搜寻性能提示,并尽可能多地应用它们。然而,我发现先分析问题更有用。

获取一些数据

为了更好地了解问题,我首先需要获得一些数字。在处理性能问题时,第一个要回答的问题是“大部分时间花在了哪里?”这通常也指向了浪费时间最多的地方。

起初,我开始使用 Visual Studio 2010 分析我们的应用程序,但这并没有给我们带来多少用处,因为它没有显示与服务通信的任何数据。我确实觉得有必要链接一些关于分析 Silverlight 应用程序的有用信息,因为 IDE 目前还不支持,而且你需要经过一些麻烦才能让它真正显示你代码的任何性能数据。你可以在这里找到 Oren Nachman 关于此主题的不错文章:这里

我应该提到,分析我们的应用程序揭示了一个创建动态对象的微小性能问题,我们使用它根据授权和应用程序设置实例化应用程序中的模块。然而,从整体上看,这与我们几乎无关,而且很难以一种好的方式来解决它。

我的下一步是实际获取关于与我们服务通信的数据。由于我们以一种方式设计了应用程序,即我们将服务连接抽象到单独的通用类中,并使用通用的服务调用来获取数据,对我来说最简单的方法是简单地记录 `DateTime.Now.Ticks`。为了尽可能降低对任何生产环境的影响,我决定通过 `Debug.WriteLine` 将此信息写入 VS 调试窗口。此外,为了确保可以轻松地关闭此功能,以便允许其他详细的调试信息,我引入了一个名为 `DEBUGGINGPERFORMANCE` 的条件编译符号,并用它包装了所有的记录代码。代码对我来说看起来是这样的:

   1: #if DEBUGGINGPERFORMANCE
   2: Debug.WriteLine(
          "BusinessLayerConnection.GetDataAsync<{0}>({1}): {2}",
          typeof(T).Name, id, DateTime.Now.Ticks);
      3: #endif

运行带有此调试代码的应用程序为我提供了有趣的数据,指向了一个特定的操作,该操作一次性获取特定模块的所有数据。在这些模块中,有一个模块花费的时间远远超过了其他模块,所以我决定进一步研究。我决定使用 Fiddler 来确保这实际上并不是由带宽问题引起的。统计数据清楚地表明,问题在于服务在实际开始发送响应之前花费了大量时间。

这让我把类似的代码放到了 web 服务中。请注意,`Debug.WriteLine` 默认不支持格式化方法,你应该将其包装在 `string.Format` 中。为了进行更好的性能测试,我决定构建一个小型测试工具,只需连续多次执行相同的服务调用。这确保了我们使用的是正确的数据,而不是追逐一些随机的外部因素。

当我开始这样测试时,我发现在调试输出中有很多消息说明“发生了类型为‘’的第一次机会异常”。在这种情况下,大部分异常与我们使用的另一个服务有关,该服务无法访问我们的一个数据库。这显然是一个简单的修复,并且显著提高了性能。

修复一个常见的 Entity Framework 问题

在解决了一个异常之后,我发现还有一些异常正在抛出。显示的具体消息是:*发生了类型为‘System.NotSupportedException’的第一次机会异常,在 mscorlib.dll 中。* 性能数据显示,每次请求中大约 3 秒钟会发生六次这样的异常!

看到这样的异常让我觉得很奇怪。网络搜索并没有返回任何有用的信息,除了它可能与 Entity Framework 有关。一个论坛线程指向了这个关于动态程序集的 bug 报告,这个报告本身也没有提供太多有用的信息。

我也在互联网上看到了人们对此消息的提问,但都没有真正的解决方案。我在 Visual Studio 中打开了 `System.NotSupportedException`,将其附加到服务进行调试,然后再次运行我的测试。正如预期的那样,它在调试器中停止在抛出异常的地方。正如预期的那样,它停止在 *mscorlib.dll* 中,在这种情况下是在 `System.Reflection.Emit.AssemblyBuilder` 类中的 `GetManifestResourceNames()` 方法中。这确实证实了它实际上是由我前面提到的 bug 引起的,但它并没有真正帮助我们解决问题。现在的问题是,“为什么有人会调用一个动态程序集上的这个方法?”

向上浏览调用堆栈可以清楚地看到,这段代码是从 Entity Framework 中调用的。事实上,它是从 `System.Data.Objects.ObjectContext` 构造函数调用的,但是为什么呢?仔细查看调用堆栈,有一些迹象表明这一定与 Entity Framework 所需的元数据初始化有关。它还显示异常发生在 `System.Data.EntityClient.EntityConnection` 类的一个名为 `SplitPaths` 的方法中。查看该方法中的代码以及异常发生时的状态,实际上让我看到了问题的原因。在 `SplitPaths` 方法中,有一个 `string[]` 称为 `results`(名字听起来怎么样?),其中包含三个 `string`:*res://*/Model.csdl*、*res://*/Model.ssdl* 和 *res://*/Model.mdl*。

听起来是不是很熟悉?那是因为这些 URL 在 Entity Framework 的默认连接字符串中可以找到,并且它们应该指向构成 Entity Framework 元数据的三个文件。有关更多文档,请参阅此处

实际上发生的是,当 Entity Framework 代码根据其连接字符串在所有已加载的资源中查找元数据文件时,它被要求一个动态程序集提供其资源。在这种情况下加载的动态程序集是托管我们 DAL 的 WCF web 服务的二进制文件。修复方法很简单。只需在每个元数据文件的连接字符串中通过其完整名称指向确切的程序集,问题就解决了。

我希望你觉得这次寻找修复方法的经历和我一样有益,并希望它能帮助到你。如果你有任何问题,请留言。

© . All rights reserved.