文介绍了如何通过异步方法消除使用 Microsoft ASPNET 的 Web 服务调用的性能问题和线程池资源的消耗问题
情况从 ASPNET 页面调用 Web 服务时的性能破坏
我们在本文中讨论 Web 服务时期望在各种情况下都可以享用 Web 服务一个主要的情况是从中间层环境(如 ASPNET Web 页面)访问 Web 服务为 MapPoint NET Web 服务的用户提供支持的人员经常收到这样的问题即用户在使用其 Web 服务时对 MapPoint NET 的调用可能需要相当长的时间这本身并不是什么问题但某些其他因素可以使之成为比表面上要严重得多的大问题
HTTP 双连接限制
HTTP 规范表明一个 HTTP 客户端与任一服务器最多可以同时建立两个 TCP 连接这可以防止单个浏览器在浏览某个页面(例如具有 个嵌入的缩略图)时由于连接请求过多而使服务器负载过重此时浏览器将仅创建 个连接然后通过这两个管道开始发送 个 HTTP 请求而不是创建 个 TCP 连接并通过每个连接来发送 HTTP 请求对于中间层此方法的问题在于中间层可能会有 个同时请求连接的用户如果不得不为每个用户进行一次 MapPoint NET Web 服务调用将会有 个用户等待两个管道中的一个空闲下来
线程池限制
ASPNET 处理传入的请求的方式是通过一个称为进程线程池的一组线程为其提供服务正常情况下请求传入后池中某个空闲的线程将为其提供服务这里的问题在于进程线程池不会创建无数个线程来处理大量的请求具有最大线程数限制是一件好事因为如果我们无限地创建线程计算机上的全部资源将只能用来管理这些线程了通过限制所能创建的线程数我们可以把线程管理的系统开销保持在一个可控的水平如果某个请求传入时线程池中的所有线程都被占用则该请求将排队等候在忙线程完成任务后空闲出来的线程才能处理新请求此方法实际上比切换到某个新线程更有效因为不需要在请求之间进行线程切换但存在的问题是如果线程的使用效率不高(尤其是在非常忙的 Web 服务器上)则等候的请求队列会变得很大
考虑一下从 ASPNET 页面进行 Web 服务调用的情况如果进行同步调用则正在运行的线程将被阻塞直到 Web 服务调用完成为止在调用期间线程无法进行任何其他活动它无法处理其他请求只能等待如果某个单处理器计算机上具有默认的工作线程数 则只需 个同时进行的请求即可用完全部线程以后的请求必须排队等候
该问题不仅限于 Web 服务
不仅调用 Web 服务的用户会遇到从 Web 页面进行调用时的拥堵且耗时较长的问题进行任意数量的较长的调用都会遇到同样的问题例如SQL Server? 请求长文件的读取或写入各种 Web 请求或访问某个并发资源(其中锁定会造成严重的延迟)实际上有许多使用 Web 服务的情况其服务调用比较迅速并不是什么问题但您或许会理解如果您想通过代理服务器调用 MapPoint NET Web 服务所使用的连接具有一定的延迟同时相应的服务可能又要花费一些时间来处理请求则您可能在各处位置都看到延迟的情况并且如果站点很忙便可能出现问题
改善问题
该问题的某些方面可以通过对环境进行某些配置设置来改善我们看一下可用于改善该问题的某些配置设置
maxconnections
连接到 Web 资源的默认双连接限制可以通过一个名为 connectionManagement 的配置元素来控制connectionManagement 设置允许您添加要让其采用非默认连接限制的站点的名称可以将以下内容添加到典型的 nfig 文件中将您连接的所有服务器的连接限制默认值增加到
<configuration>
<>
<connectionManagement>
<add address=* maxconnection= />
</connectionManagement>
</>
<systemweb>
应当注意的是对本地计算机的连接数量从来都没有限制因此如果是连接到本地主机则此设置无效
maxWorkerThreads 和 minFreeThreads
如果收到 HTTP 错误(服务暂时过载)则表明线程池中的线程已全部占用并且请求队列也已超出最大值(appRequestQueueLimit 的默认设置为 )对于 IIS 安装可以简单地增加线程池的大小而对于 IIS 安装(与 IIS 不兼容)这些设置将无效
maxWorkerThreads 和 maxIoThreads 分别控制工作线程数以及处理新提交的 ASPNET 请求的线程数这些设置需要在您的 nfig 中进行配置它们将影响您计算机上运行的所有 Web 应用程序maxWorkerThreads 是 nfig 中的 processModel 元素的一部分并且您在查看后会发现该设置的默认值为每个处理器 个线程
minFreeThreads 设置可以在 nfig 中进行配置或者在您的应用程序的 nfig 文件中的 httpRuntime 元素下进行配置该设置的作用是当空闲的线程数低于所设置的限制时将禁止使用线程池中的线程来处理传入的 HTTP 请求如果您需要某个进程线程池线程完成挂起的请求这会很有用如果所有的线程都被用来处理传入的 HTTP 请求并且这些请求在等待另一个线程完成其处理那么就会进入死锁状态例如如果您正在从 ASPNET 应用程序进行对某个 Web 服务的异步 Web 服务调用并且在等待回调函数完成该请求就会出现这种情况因为回调必须在进程线程池中的空闲线程上进行如果查看一下您的 nfig将会注意到 minFreeThreads 设置的默认值为 如果工作线程池的限制为 则该默认值还可以满足需要但是如果线程池的大小增加到 该默认值就太小了
应当注意的是如果您的 ASPNET 应用程序对本地计算机进行 Web 服务调用则线程池限制的问题将被激化例如我为此专栏创建的测试应用程序调用与 ASPX 页面同处一台计算机上的 Web 服务因而对于阻塞的调用一个线程被同时用于 ASPX 页面和 ASMX Web 服务请求这有效地使 Web 服务器处理的同时请求数增加了一倍在同时进行两个 Web 服务请求(使用异步 Web 服务调用)的情况下我们最终使同时进行的请求数增加了两倍为避免在回调本地计算机时出现此类问题您应当考虑您的应用程序的体系结构使其简单地直接从 ASPX 代码来执行 Web 方法中的代码
Windows XP 限制
我们必须要注意如果您在一个 Windows? XP 计算机上进行某项测试则所面临的另一个限制是 XP Web 服务器对所允许的同时连接数的人为限制因为 Windows XP 不是服务器平台其同时连接数被限制为 这对于开发环境中的测试通常没问题但是如果试图进行任何复杂的测试该限制问题就会比较严重本地计算机的连接不受此限制影响
真正的解决方案异步请求处理
调整配置设置是一种改善问题的方法而在实际设计 Web 应用程序时通过某种方式彻底解决问题则是另一回事等待阻塞的调用完成的线程永远也不会有更好的调整余地因此解决的办法是完全避免阻塞问题异步处理请求就是一个适当的解决方案这表现在两个方面进行异步 Web 服务调用以及在 ASPNET Web 应用程序中异步处理请求
异步 Web 服务调用
在以前的专栏中我写了有关异步调用 Web 服务的问题能够使线程不用等待 Web 服务调用完成是创建释放线程以便处理更多请求的异步页面处理模型的关键部分此外异步调用 Web 服务也比较简单
请考虑以下 ASPX 页面的 Visual BasicNET 代码
错用同步 Web 服务调用所造成的性能极差的
页面!
Public Class SyncPage
Inherits SystemWebUIPage
Protected WithEvents Label As SystemWebUIWebControlsLabel
Protected WithEvents Label As SystemWebUIWebControlsLabel
Private Sub Page_Load(ByVal sender As SystemObject _
ByVal e As SystemEventArgs) Handles MyBaseLoad
调用 Web 服务
Dim proxy As New localhostService
LabelText = proxyMethod()
LabelText = proxyMethod()
End Sub
End Class
此代码非常易懂页面加载时将创建一个 Web 服务代理实例然后用该实例两次调用一个名为 Method 的 Web 方法Method 只返回包含传递给该方法的输入参数的字符串为了向该系统添加一定程度的延迟Method 在返回字符串之前还休眠了 秒钟从调用返回到 Method 的字符串被放在 ASPX 页面上的两个标签的文本中该页面提供的性能极差并且像一块海绵一样从进程线程池中吸取线程由于在 Method Web 方法中有 秒钟的延迟对该页面的一个调用至少要 秒钟才能完成
以下代码片段显示了一个类似 Web 页面的代码只不过现在进行的是异步 Web 服务调用
Public Class AsyncPage
Inherits SystemWebUIPage
Protected WithEvents Label As SystemWebUIWebControlsLabel
Protected WithEvents Label As SystemWebUIWebControlsLabel
Private Sub Page_Load(ByVal sender As SystemObject _
ByVal e As SystemEventArgs) Handles MyBaseLoad
调用 Web 服务
Dim proxy As New localhostService
Dim res As IAsyncResult
= proxyBeginMethod( Nothing Noth