我有以下WebApi方法从RavenDB返回无限制的结果流:
public IEnumerable<Foo> Get()
{
var query = DocumentSession.Query<Foo, FooIndex>();
using (var enumerator = DocumentSession.Advanced.Stream(query))
while (enumerator.MoveNext())
yield return enumerator.Current.Document;
}
现在我想让那个异步。天真的方法当然不起作用:
public async Task<IEnumerable<Location>> Get()
{
var query = AsyncDocumentSession.Query<Foo, FooIndex>();
using (var enumerator = await AsyncDocumentSession.Advanced.StreamAsync(query))
while (await enumerator.MoveNextAsync())
yield return enumerator.Current.Document;
}
...因为该方法既不是异步又是迭代器。
答案 0 :(得分:10)
由于这是一个WebAPI操作方法,因此HTTP会将您限制为单个响应。如果您只返回IEnumerable<T>
,那么ASP.NET将在内存中枚举它,然后发送响应。
如果你对这个内存中的过程很好,那么你可以自己做同样的事情:
public async Task<List<Location>> Get()
{
var result = new List<Location>();
var query = AsyncDocumentSession.Query<Foo, FooIndex>();
using (var enumerator = await AsyncDocumentSession.Advanced.StreamAsync(query))
while (await enumerator.MoveNextAsync())
result.Add(enumerator.Current.Document);
return result;
}
但是,我认为最好使用流式响应,您可以通过PushStreamContent
获得;像这样的东西:
public HttpResponseMessage Get()
{
var query = AsyncDocumentSession.Query<Foo, FooIndex>();
HttpResponseMessage response = Request.CreateResponse();
response.Content = new PushStreamContent(
async (stream, content, context) =>
{
using (stream)
using (var enumerator = await AsyncDocumentSession.Advanced.StreamAsync(query))
{
while (await enumerator.MoveNextAsync())
{
// TODO: adjust encoding as necessary.
var serialized = JsonConvert.SerializeObject(enumerator.CurrentDocument);
var data = UTF8Encoding.UTF8.GetBytes(serialized);
var countPrefix = BitConverter.GetBytes(data.Length);
await stream.WriteAsync(countPrefix, 0, countPrefix.Length);
await stream.WriteAsync(data, 0, data.Length);
}
}
});
return response;
}
流式响应不需要服务器将整个响应保存在内存中;但是,您必须决定将文档写入响应流的正确方法。上面的示例代码只是将它们转换为JSON,以UTF8编码,(二进制)长度 - 前缀这些字符串。
答案 1 :(得分:1)
您可以实现自己的迭代器,而不是让编译器为您生成一个迭代器。
但是,在迭代器上调用MoveNext
也必须是异步的 - 这意味着你不能实现IEnumerable<T>
`IEnumerator , you'd have to define your own interface, e.g.,
IAsyncEnumerator`。
而且你也无法在foreach循环中使用该迭代器。
我看到它的方式,你最好的办法是做StreamAsync
所做的事情。创建一个自定义类型IAsyncEnumerable
,返回实现自定义IAsyncEnumerator<T>
方法的async T MoveNextAsync()
。枚举将包装您的query
对象,枚举器将获取文档会话的文档。
internal class AsyncDocumentEnumerable : IAsyncEnumerable<Document>
{
private readonly YourQueryType _query;
public AsyncDocumentEnumerable(YourQueryType query)
{
_query = query;
}
IAsyncEnumerator<Document> GetEnumerator()
{
return new AsyncDocumentEnumerator(_query);
}
}
internal class AsyncDocumentEnumerator : IAsyncDocumentEnumerator<Document>
{
private readonly YourQueryType _query;
private IAsyncEnumerator<DocumentSession> _iter;
public AsyncDocumentEnumerator(YourQueryType query)
{
_query = query;
}
public Task<bool> async MoveNextAsync()
{
if(_iter == null)
_iter = await AsyncDocumentSession.Advanced.StreamAsync(query);
bool moved = await _iter.MoveNextAsync();
if(moved)
Current = _iter.Current.Document;
return moved;
}
public Document Current{get; private set;}
}
答案 2 :(得分:1)
在C#8中,他们引入了IAsyncEnumerable<int>
async IAsyncEnumerable<int> GetBigResultsAsync()
{
await foreach (var result in GetResultsAsync())
{
if (result > 20) yield return result;
}
}
答案 3 :(得分:0)
您可以查看ReactiveExtensions for .Net,它们是专为您的需求而设计的。最终结果可能如下所示:
public IObservable<Location> Get()
{
var locations = new Subject<Location>();
Task.Run(() =>
{
var query = DocumentSession.Query<Foo, FooIndex>();
foreach (var document in DocumentSession.Advanced.Stream(query))
{
locations.OnNext(document);
}
locations.OnCompleted();
});
return locations;
}
答案 4 :(得分:0)
毕竟不是那么难。解决方案是一个格式化程序,它可以异步处理枚举器并将JSON写入流:
public class CustomJsonMediaTypeFormatter : JsonMediaTypeFormatter
{
public override async Task WriteToStreamAsync(
Type type, object value, Stream writeStream, HttpContent content,
TransportContext transportContext, CancellationToken cancellationToken)
{
if (type.IsGenericType &&
type.GetGenericTypeDefinition() == typeof(IAsyncEnumerator<>))
{
var writer = new JsonTextWriter(new StreamWriter(writeStream))
{ CloseOutput = false };
writer.WriteStartArray();
await Serialize((dynamic)value, writer);
writer.WriteEndArray();
writer.Flush();
}
else
await base.WriteToStreamAsync(type, value, writeStream, content,
transportContext, cancellationToken);
}
async Task Serialize<T>(IAsyncEnumerator<StreamResult<T>> enumerator,
JsonTextWriter writer)
{
var serializer = JsonSerializer.Create(SerializerSettings);
while (await enumerator.MoveNextAsync())
serializer.Serialize(writer, enumerator.Current.Document);
}
}
现在我的WebApi方法比以前更短了:
public Task<IAsyncEnumerator<StreamResult<Foo>>> Get()
{
var query = AsyncDocumentSession.Query<Foo, FooIndex>();
return AsyncDocumentSession.Advanced.StreamAsync(query);
}