C# protobuf网络流在返回数据之前被释放
我有一个webapi,它在从数据库列转换后返回一些protobuf net序列化项。项目的数量可能相当大,因此我希望避免在内存中具体化它们,而是将它们流出来。然而,在流抛出一个已处理的异常之前,我从未得到一个项目。如果我将列表具体化,它确实有效,但我希望避免它。 以下是我正在做的:C# protobuf网络流在返回数据之前被释放,c#,asp.net-web-api,ado.net,protobuf-net,sqlclient,C#,Asp.net Web Api,Ado.net,Protobuf Net,Sqlclient,我有一个webapi,它在从数据库列转换后返回一些protobuf net序列化项。项目的数量可能相当大,因此我希望避免在内存中具体化它们,而是将它们流出来。然而,在流抛出一个已处理的异常之前,我从未得到一个项目。如果我将列表具体化,它确实有效,但我希望避免它。 以下是我正在做的: private override async Task<IEnumerable<MyObj>> GetRawData(int id) { using(var sqlConn = Get
private override async Task<IEnumerable<MyObj>> GetRawData(int id)
{
using(var sqlConn = GetOpenConnection())
{
using (var sqlReader =(await sqlConn.ExecuteReaderAsync(
_workingQuery,new {id = id}, 60)
.ConfigureAwait(false) as SqlDataReader))
{
await sqlReader.ReadAsync().ConfigureAwait(false);
using (var stream = sqlReader.GetStream(0))
{
return _serializerModel.DeserializeItems<MyObj>(stream, PrefixStyle.Base128, 1)
}
}
}
}
private async Task<IHttpActionResult> TransformData(int id)
{
var data=await GetRawData().ConfigureAwait(false);
return Ok(new {Data=data.Where(m=>...).Select(m=>...)})
}
[HttpGet,Route("api/myroute/{id}"]
public async Task<IHttpActionResult> GetObjs(int id)
{
return await TransformData();
}
private override异步任务GetRawData(int-id)
{
使用(var sqlConn=GetOpenConnection())
{
使用(var sqlReader=(wait sqlConn.ExecuteReaderAsync(
_workingQuery,新的{id=id},60)
.ConfigureAwait(false)为SqlDataReader)
{
等待sqlReader.ReadAsync().ConfigureAwait(false);
使用(var stream=sqlReader.GetStream(0))
{
return _serializerModel.DeserializeItems(流,PrefixStyle.Base128,1)
}
}
}
}
专用异步任务TransformData(int id)
{
var data=await GetRawData().ConfigureAwait(false);
返回Ok(新建{Data=Data.Where(m=>…)。选择(m=>…)})
}
[HttpGet,Route(“api/myroute/{id}”]
公共异步任务GetObjs(int-id)
{
return wait TransformData();
}
但是,我最终在读取已处理流时出错。如何避免此错误?这一行
return _serializerModel.DeserializeItems<MyObj>(stream, PrefixStyle.Base128, 1)
它的长与短之处在于,您正在返回一个非枚举序列,并在它到达调用方之前关闭(处理)它所需的所有内容。您需要:
- 急切地枚举序列(内存中的缓冲区)-例如,添加
.ToList()
- 重新构造代码,以便在迭代结束之前不会处理任何内容
wait
之后)
bool dispose = true;
SqlConnection conn = null;
//...others
try {
conn = ...
...await...
var result = InnerMethod(conn, stream, ..., )
// ^^^ everything now known / obtained from DB
dispose = false; // handing lifetime management to the inner method
return result;
} finally {
if(dispose) {
using(conn) {}
//.//
}
}
IEnumerable<...> Inner(...) { // not async
using (conn)
using (stream) {
foreach(var item in ...) {
yield return item;
}
}
}
bool dispose=true;
SqlConnection-conn=null;
//……其他
试一试{
康涅狄格州=。。。
…等待。。。
var结果=内部方法(连接、流等)
//^^^目前已知/从DB获得的所有信息
dispose=false;//将生存期管理交给内部方法
返回结果;
}最后{
如果(处置){
使用(conn){}
//.//
}
}
IEnumerable内部(…){//不异步
使用(康涅狄格州)
使用(流){
foreach(var项在…){
收益回报项目;
}
}
}
显示serializerModel类。它是protobuf net typemodel对象。我不这么认为,因为Where和Select是不具体化枚举的惰性运算符。这些对象将具体化,但一旦写入json流,它们就可能被丢弃。如果我将它们写入列表,我将立即支付具体化的成本t then and there,然后我只能在对象都写入流之后扔掉它们。经过进一步的调查,我同意你的第一个陈述。但是第二个陈述不是真的。new{Data=…}具体化整个Json对象,然后发送它。为了流化结果,您需要让Web API返回一个流。问题仍然是,在流被读取时,最终不处理流、读取器或连接对象。我可以在某种程度上解决这个问题,但我不希望控制器处理连接。asGetRawData
应该拥有它,仅仅处理流不会处理连接。推送内容流是一个有趣的想法。使用我在回答的后半部分中建议的方法,您可以有一个单独的函数读取ReaderStream并将WriterStream返回到响应。控制器将till负责关闭WriterStream,关闭WriterStream也会关闭ReaderStream,但没什么可做的。谢谢marc。我最终采用了一种非常类似的方法。我很好奇,为什么返回一个调用yield return iterator块的函数可以让这一切在返回同样懒惰的iterat块的情况下发挥作用或者反序列化项的导致它立即被处置。是因为我将资源的处置与迭代的结束捆绑在一起吗?
bool dispose = true;
SqlConnection conn = null;
//...others
try {
conn = ...
...await...
var result = InnerMethod(conn, stream, ..., )
// ^^^ everything now known / obtained from DB
dispose = false; // handing lifetime management to the inner method
return result;
} finally {
if(dispose) {
using(conn) {}
//.//
}
}
IEnumerable<...> Inner(...) { // not async
using (conn)
using (stream) {
foreach(var item in ...) {
yield return item;
}
}
}