本文继续讨论关于 Web 主机特性的文章,关注当 WCF 托管在 IIS 时,其改进的流式传输能力。
在 .NET 4.5 之前的版本中,如果尝试创建一个使用流式请求的 WCF 服务(例如,文件上传服务),并将其托管在 IIS 中,可能会发现 WCF 服务表现出一种奇怪的行为——它似乎在接收请求时有所延迟,就好像请求被完全加载到内存中,然后才传递给 WCF。那么,它是流式的吗?还是实际上是缓冲的?答案是两者都有。
当在 IIS 中托管一个 WCF 服务时,即使不使用 ASP.NET 兼容性模式,也会得到一部分 ASP.NET 管道。这在 MSDN 上的 文章中有文档记录(查看关于 PostAuthenticateRequest 事件的部分)。在 .NET 4 中,ASP.NET 的设计缺陷导致发送到 WCF 的请求在 ASP.NET 中被缓冲。这种缓冲行为会导致几个主要的副作用:
流式消息被 ASP.NET 接收到的时间与 WCF 服务方法实际被调用的时间之间存在延迟。 由于缓冲,内存消耗会增加——消耗的内存量取决于客户端发送的消息大小,但如果增加 ASP.NET 的 MaxRequestLength、IIS 7 的 MaxAllowedContentLength 以及 WCF 的 MaxReceivedMessageSize 和 MaxBufferSize,内存消耗甚至可以达到几百 MB。
当 ASP.NET 缓冲请求时,它会同时使用内存和磁盘。ASP.NET 的 requestLengthDiskThreshold 配置设置控制 ASP.NET 何时开始使用磁盘。如果一次上传多个文件到 WCF,会开始看到由于多个文件同时写入磁盘而导致的延迟。顺便说一下,文件被写入到 Web 应用程序的临时 ASP.NET 文件夹下的 "upload" 文件夹中(位于 c:\windows\Microsoft.NET\Framework\vX.X.XXXX\Temporary ASP.NET Files\),并在请求处理后被移除。
为了展示这种行为,创建了一个客户端应用程序,它向 WCF 服务上传了一个 500MB 的文件。WCF 服务托管在 IIS 中,并设置为流式请求(可以从这里下载 )。以下输出显示了服务接收和处理请求所需的时间以及消耗的内存的一些信息:
Client started upload on 17/01/2012 19:03:25
Available memory before starting is: 2701MB
Client finished upload on 17/01/2012 19:03:44
Available memory after finishing is: 2699MB
Available memory on ASP.NET is: 2701MB
ASP.NET received upload at: 17/01/2012 19:03:28
Available memory on WCF is: 2122MB
WCF started receiving file at: 17/01/2012 19:03:38
WCF finished receiving file at: 17/01/2012 19:03:43
File size is: 524288000
Press any key to continue . . .
关于这些结果的一些注意事项: 客户端开始/客户端完成(第 1+3 行)——客户端等待服务的总时间是 19 秒;这包括上传时间、ASP.NET 的缓冲时间以及 WCF 处理接收到的流的时间。 ASP.NET 在客户端开始发送流 3 秒后开始接收流(第 6 行)。 WCF 在 ASP.NET 开始接收流 10 秒后开始接收流,并且从客户端开始发送流的总时间是 13 秒(第 8 行)。总的来说,WCF 从 ASP.NET 读取整个流需要 5 秒(第 8+9 行)。
在客户端发送消息之前,机器上的可用内存为 2701MB,这也是 ASP.NET 首次接收消息时的可用内存。当 WCF 接收到请求并开始处理时,可用内存为 2122MB——大约消耗了 580MB 的内存用于此操作(第 2、5 和 7 行)。
至于生成的临时文件,这里是临时 ASP.NET 文件夹内容的截图:
注意:为了显示 ASP.NET 信息,使用了 ASP.NET 兼容性模式。如果想验证在不使用兼容性模式时问题也存在,可以在示例代码中关闭它(看看客户端发送请求的时间和 WCF 实际开始处理请求的时间之间的差异——应该有很大的延迟)。
所以,得到 WCF 4 在 IIS 上处理流式内容并不好,但 WCF 4.5 呢?有什么变化?
在 WCF 4.5 中,这种情况就不会发生——在 .NET 4.5 中,ASP.NET 不会缓冲请求,而是直接将其转发给 WCF,所以没有延迟,没有内存消耗,也没有磁盘使用。
想看证据吗?在 Windows Server 8 上运行了相同的演示代码,使用 WCF 4.5 在 IIS 上。使用了较小的文件大小(200MB),因为这是一个内存较少的 VM,但仍然可以清楚地看到差异:
Client started upload on 11/27/2011 7:23:18 AM
Available memory before starting is: 942MB
Client finished upload on 11/27/2011 7:23:46 AM
Available memory after finishing is: 942MB
Available memory on ASP.NET is: 941MB
ASP.NET received upload at: 11/27/2011 7:23:20 AM
Available memory on WCF is: 942MB
WCF started receiving file at: 11/27/2011 7:23:20 AM
WCF finished receiving file at: 11/27/2011 7:23:46 AM
File size is: 209715200
Press any key to continue . . .
首先要注意的是——在整个执行过程中内存消耗没有变化——保持在大约 942MB(第 2+4+5+7 行)。
至于延迟——WCF 在 ASP.NET 接收请求的同一时间接收请求(第 6+8 行),这是在客户端开始发送后 2 秒。
哦,由于 ASP.NET 直接将流传递给 WCF,所以没有创建临时文件!!