Asynchronously flush the HttpResponseStreamWriter after a view has been rendered.
This solves a perf issue for views which produce content that is smaller than the buffer size of HttpResponseStreamWriter. In this case, the writer ends up synchronously writing to the Response as part of Dispose which affects perf.
This commit is contained in:
parent
8a502dbe5d
commit
6a0a24481a
|
|
@ -15,7 +15,10 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// </summary>
|
||||
public class HttpResponseStreamWriter : TextWriter
|
||||
{
|
||||
private const int DefaultBufferSize = 1024;
|
||||
/// <summary>
|
||||
/// Default buffer size.
|
||||
/// </summary>
|
||||
public const int DefaultBufferSize = 1024;
|
||||
private readonly Stream _stream;
|
||||
private Encoder _encoder;
|
||||
private byte[] _byteBuffer;
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
htmlHelperOptions);
|
||||
|
||||
await view.RenderAsync(viewContext);
|
||||
// Invoke FlushAsync to ensure any buffered content is asynchronously written to the underlying
|
||||
// response. In the absence of this line, the buffer gets synchronously written to the response
|
||||
// as part of the Dispose which has a perf impact.
|
||||
await writer.FlushAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.AspNet.Mvc.Abstractions;
|
||||
|
|
@ -89,7 +90,6 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
||||
.Callback((ViewContext v) =>
|
||||
{
|
||||
view.ToString();
|
||||
v.Writer.Write("abcd");
|
||||
})
|
||||
.Returns(Task.FromResult(0));
|
||||
|
|
@ -99,9 +99,10 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
context.Response.Body = memoryStream;
|
||||
context.Response.ContentType = responseContentType;
|
||||
|
||||
var actionContext = new ActionContext(context,
|
||||
new RouteData(),
|
||||
new ActionDescriptor());
|
||||
var actionContext = new ActionContext(
|
||||
context,
|
||||
new RouteData(),
|
||||
new ActionDescriptor());
|
||||
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
|
||||
|
||||
// Act
|
||||
|
|
@ -135,9 +136,10 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
var memoryStream = new MemoryStream();
|
||||
context.Response.Body = memoryStream;
|
||||
|
||||
var actionContext = new ActionContext(context,
|
||||
new RouteData(),
|
||||
new ActionDescriptor());
|
||||
var actionContext = new ActionContext(
|
||||
context,
|
||||
new RouteData(),
|
||||
new ActionDescriptor());
|
||||
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
|
||||
|
||||
// Act
|
||||
|
|
@ -153,5 +155,45 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
// Assert
|
||||
Assert.Equal(expectedLength, memoryStream.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(HttpResponseStreamWriter.DefaultBufferSize - 1)]
|
||||
[InlineData(HttpResponseStreamWriter.DefaultBufferSize + 1)]
|
||||
[InlineData(2 * HttpResponseStreamWriter.DefaultBufferSize + 4)]
|
||||
public async Task ExecuteAsync_AsynchronouslyFlushesToTheResponseStream_PriorToDispose(int writeLength)
|
||||
{
|
||||
// Arrange
|
||||
var view = new Mock<IView>();
|
||||
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
||||
.Returns((ViewContext v) =>
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var text = new string('a', writeLength);
|
||||
await v.Writer.WriteAsync(text);
|
||||
}));
|
||||
|
||||
var context = new DefaultHttpContext();
|
||||
var stream = new Mock<Stream>();
|
||||
context.Response.Body = stream.Object;
|
||||
|
||||
var actionContext = new ActionContext(
|
||||
context,
|
||||
new RouteData(),
|
||||
new ActionDescriptor());
|
||||
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
|
||||
|
||||
// Act
|
||||
await ViewExecutor.ExecuteAsync(
|
||||
view.Object,
|
||||
actionContext,
|
||||
viewData,
|
||||
Mock.Of<ITempDataDictionary>(),
|
||||
new HtmlHelperOptions(),
|
||||
ViewExecutor.DefaultContentType);
|
||||
|
||||
// Assert
|
||||
stream.Verify(s => s.FlushAsync(It.IsAny<CancellationToken>()), Times.Once());
|
||||
stream.Verify(s => s.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Never());
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue