在一个 ASP.NET Core 项目的日志中发现下面的错误:
Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
异常堆栈如下:
Microsoft.AspNetCore.Server.Kestrel
System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseStream.Write(Byte[] buffer, Int32 offset, Int32 count)
at Microsoft.AspNetCore.ResponseCaching.ResponseCachingStream.Write(Byte[] buffer, Int32 offset, Int32 count)
at Microsoft.AspNetCore.WebUtilities.HttpResponseStreamWriter.Write(String value)
at Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers.ViewBuffer.WriteToAsync(TextWriter writer, HtmlEncoder encoder)
at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewComponentResultExecutor.ExecuteAsync(ActionContext context, ViewComponentResult result)
at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewComponentResultExecutor.ExecuteAsync(ActionContext context, ViewComponentResult result)
AllowSynchronousIO = true
的方法IHtmlContent
时触发的(ViewBuffer.cs#L258)if (value.Value is IHtmlContent valueAsHtmlContent)
{
valueAsHtmlContent.WriteTo(writer, encoder);
await writer.FlushAsync();
continue;
}
【更新】
通过 github issue 评论中提供的重现问题的代码发现,当 View Component 视图中要输出的字符串超过 16K
就会触发这个问题
public class HomeController : Controller
{
public IActionResult Fail()
{
return ViewComponent(typeof(FailingViewComponent));
}
}
public class FailingViewComponent : ViewComponent
{
public IHtmlContent Invoke()
{
return new HtmlString(new string('X', 16385)); // 16384 is OK
}
}
终于找到了原因!是 MemoryPoolHttpResponseStreamWriterFactory.cs#L28 中的 DefaultBufferSize
引起的
public const int DefaultBufferSize = 16 * 1024;
自己实现一个 CustomMemoryPoolHttpResponseStreamWriterFactory
修改 DefaultBufferSize
的值
public class CustomMemoryPoolHttpResponseStreamWriterFactory : IHttpResponseStreamWriterFactory
{
public const int DefaultBufferSize = 32 * 1024;
private readonly ArrayPool<byte> _bytePool;
private readonly ArrayPool<char> _charPool;
public CustomMemoryPoolHttpResponseStreamWriterFactory(
ArrayPool<byte> bytePool,
ArrayPool<char> charPool)
{
_bytePool = bytePool;
_charPool = charPool;
}
public TextWriter CreateWriter(Stream stream, Encoding encoding)
{
return new HttpResponseStreamWriter(stream, encoding, DefaultBufferSize, _bytePool, _charPool);
}
}
替换掉 MemoryPoolHttpResponseStreamWriterFactory
builder.Services.TryAddSingleton<IHttpResponseStreamWriterFactory, CustomMemoryPoolHttpResponseStreamWriterFactory>();
问题就解决了
// FailingViewComponent no longer failed
public class FailingViewComponent : ViewComponent
{
public IHtmlContent Invoke()
{
// 16385 is OK with CustomMemoryPoolHttpResponseStreamWriterFactory
return new HtmlString(new string('X', 16385));
}
}
有个变通的解决方法,可以将需要渲染的大于 16k 的 html 字符串分片处理
<div id="sidebar_news_content">
foreach (var chuck in Model.Blog.SplitNews())
{
@Html.Raw(chuck);
}
</div>
public IEnumerable<string> SplitNews(int chunkSize = 16 * 1024)
{
for (int i = 0; i < News.Length; i += chunkSize)
{
yield return News.Substring(i, Math.Min(chunkSize, News.Length - i));
}
}
valueAsHtmlContent.WriteTo(writer, encoder); 这个方法看看有没有异步写法
这是 ASP.NET Core 的源码,没法动
异常是在 ViewBuffer.cs#L258 抛出的
– dudu 1年前
– dudu 1年前ViewBuffer
也实现了IHtmlContent
接口:ViewBuffer -> IHtmlContentBuilder -> IHtmlContentContainer -> IHtmlContent
– dudu 1年前value.Value
是ViewBufferValue.Value
– dudu 1年前EncodingWrapper
只实现了IHtmlContent
,new ViewBufferValue(new EncodingWrapper(unencoded))
会触发这个问题