在处理大量对象(例如10,000个对象)时,通常需要解决两大性能问题:对象的大内存分配和服务器的长时间响应。为了解决这些问题,有两种可以提高服务器端性能的方法:C#中的迭代模式和HTTP中的分块传输编码。在接下来的部分中,将探讨这些方法如何帮助解决这两个问题,并提供两个从服务器端到客户端流式传输数组的示例。
众所周知,可以通过在具有IEnumerable(T)返回类型的方法或属性中使用yield关键字来启用迭代模式。该模式的理念是逐个枚举项目,而不是返回整个集合。
public IEnumerable<ReturnModel> Get()
{
// 返回大量对象的示例
foreach (var i in Enumerable.Range(0, 10000))
{
yield return new ReturnModel() { SequenceNumber = i, ID = Guid.NewGuid() };
}
}
由于枚举在foreach循环开始时就开始,而不需要等待所有对象都准备好,通常可以预期效率和内存使用会更好。
分块传输编码是一种机制,允许服务器“逐块”返回数据。在编码中,数据由每个十六进制数字后跟一个\r\n分隔,这告诉客户端后续块的长度。下面是一个由Mozilla开发者网络提供的服务器响应示例,它由三行组成,每行都是一个块。
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
7\r\n
Mozilla\r\n
9\r\n
Developer\r\n
7\r\n
Network\r\n
0\r\n
\r\n
由于数据旨在以一系列块的形式发送,而不是整个数据,因此省略了常规的Content-Length头部。
以下代码片段展示了一个Web API方法传输大型JSON数组。Get方法以之前看过的迭代模式返回每个对象。自定义的HTTP消息处理程序将在返回流开始之前启用分块传输编码。
// 省略命名空间
namespace WebApplication1.Controllers
{
public class ValuesController : ApiController
{
[HttpGet]
public IEnumerable<ReturnModel> Get()
{
// 返回大量对象的示例
foreach (var i in Enumerable.Range(0, 10000))
{
yield return new ReturnModel() { SequenceNumber = i, ID = Guid.NewGuid() };
}
}
}
public class Handler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = base.SendAsync(request, cancellationToken);
response.Result.Headers.TransferEncodingChunked = true;
return response;
}
}
}
以下代码片段展示了如何将自定义HTTP消息处理程序添加到集合中。
// 省略命名空间
namespace WebApplication1
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API配置和服务
// Web API路由
config.MapHttpAttributeRoutes();
config.MessageHandlers.Add(new Handler());
}
}
}
// 省略命名空间
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Press any key to continue...");
Console.ReadKey(true);
foreach (var value in GetValues())
{
Console.WriteLine("{0}\t{1}", value.SequenceNumber, value.ID);
}
Console.ReadKey(true);
}
static IEnumerable<ReturnModel> GetValues()
{
var serializer = new JsonSerializer();
var client = new HttpClient();
var header = new MediaTypeWithQualityHeaderValue("application/json");
client.DefaultRequestHeaders.Accept.Add(header);
// 注意:端口号可能不同。
using (var stream = client.GetStreamAsync("http://localhost:63025/api/values").Result)
using (var sr = new StreamReader(stream))
using (var jr = new JsonTextReader(sr))
{
while (jr.Read())
{
if (jr.TokenType != JsonToken.StartArray && jr.TokenType != JsonToken.EndArray)
{
yield return serializer.Deserialize<ReturnModel>(jr);
}
}
}
}
}
}