NET 核心: 不允许同步操作。请调用 WriteAsync 或将 AllowSynchronousIO 设置为 true

NET 核心服务器,AllowSynchronousIO设置为 false

        new WebHostBuilder()
.UseKestrel(options =>
{
options.AllowSynchronousIO = false;
})

在操作中,它输出一个 JsonResult

    public async Task<IActionResult> SanityCheck()
{
Dictionary<string, string> dic = await GetDic();


return this.Json(dic);
}

结果却是个例外

System.InvalidOperationException: 同步操作是 调用 WriteAsync 或将 AllowSynchronousIO 设置为 true。

我不能用 AllowSynchronousIO=false返回一个 JSON 结果吗?

86278 次浏览

I'm not sure what your requirements are or what GetDic() does, but code like the following should absolutely work given GetDic() doesn't do any synchronous IO:

public async Task<IActionResult> SanityCheck()
{
Dictionary<string, string> dic = await GetDic();


return this.Ok(dic);
}

And if you still want to serialise dic to JSON, the following code should do:

public async Task<IActionResult> SanityCheck()
{
Dictionary<string, string> dic = await GetDic();
string json = JsonConvert.SerializeObject(dic);


return this.Ok(json);
}

Note that this last piece of code returns the result as text/plain instead of application/json.

Also, I tested this under ASP.NET Core 2.2.

You might have the following problem: https://github.com/aspnet/AspNetCore/issues/8302

And you can find more info here: https://github.com/aspnet/AspNetCore/issues/7644

A workaround until the issue is being solved is to allow Synchronous IO. Put this in Startup.cs for either Kestrel or IIS:

public void ConfigureServices(IServiceCollection services)
{
// If using Kestrel:
services.Configure<KestrelServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});


// If using IIS:
services.Configure<IISServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
}

I had this issue with my unit tests. I had to update my TestServer to AlloSynchronousIO

Server = new TestServer(new WebHostBuilder().UseStartup<Startup>());
Server.AllowSynchronousIO = true;

this code work for me, make async read:

public override async void OnActionExecuting(ActionExecutingContext filterContext)
{
string content = "";
var request = filterContext.HttpContext.Request;
try
{
request.EnableBuffering();
request.Body.Position = 0;
using (StreamReader reader = new StreamReader(request.Body, Encoding.UTF8,true,1024,true))
{
content = await reader.ReadToEndAsync();
}
}
finally
{
request.Body.Position = 0;
}
}

When the exception is thrown in code you cannot control, and you have no other choice than to enable AllowSynchronousIO, it is best to enable it for specific requests, instead of globally.

In the GitHub issue announcing this feature, the following workaround is suggested:

var syncIOFeature = HttpContext.Features.Get<IHttpBodyControlFeature>();
if (syncIOFeature != null)
{
syncIOFeature.AllowSynchronousIO = true;
}

You can create a simple middleware function to apply this to specific requests:

app.Use(async (context, next) =>
{
if (context.Request.Path.StartsWithSegments("/some-endpoint-that-needs-sync-io"))
{
var syncIoFeature = context.Features.Get<IHttpBodyControlFeature>();
if (syncIoFeature != null)
{
syncIoFeature.AllowSynchronousIO = true;
}
}


await next();
})

Based on Mark Lagendijk's answer, i applied that logic on a declarative way with an attribute:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AllowSynchronousIOAttribute : ActionFilterAttribute
{
public AllowSynchronousIOAttribute()
{
}


public override void OnResultExecuting(ResultExecutingContext context)
{
var syncIOFeature = context.HttpContext.Features.Get<IHttpBodyControlFeature>();
if (syncIOFeature != null)
{
syncIOFeature.AllowSynchronousIO = true;
}
}
}

Just use it on an action method or an entire controller to enable it:

[AllowSynchronousIO]
public IActionResult DownloadSynchronous()
{
return Something();
}

In the configure service add below code. It worked for me

 services.Configure<KestrelServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});


// If using IIS:
services.Configure<IISServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});

Everybody else is showing how to allow synchronous IO. In my case, the problem was that my custom stream classes were not implementing DisposeAsync and FlushAsync correctly. Fixing that made the error go away.

I managed to find my own unique version of this problem with the following middleware (.NET 6.0):

public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
context.Response.StatusCode = 200;
using (var writer = new StreamWriter(context.Response.Body))
{
await writer.WriteLineAsync("Done!");
return;
}
}

I spent a long time staring at this until I realised what the stack trace was telling me:

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 System.IO.Stream.Write(ReadOnlySpan`1 buffer)

at System.IO.StreamWriter.Flush(Boolean flushStream, Boolean flushEncoder)

at System.IO.StreamWriter.Dispose(Boolean disposing)

at System.IO.TextWriter.Dispose()

The important line here was the System.IO.TextWriter.Dispose(): this is causing the resulting flush and lower-level write to be called synchronously.

Fortunately, StreamWriter implements IAsyncDisposable so it's easy to solve it by adding await before using:

public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
context.Response.StatusCode = 200;
await using (var writer = new StreamWriter(context.Response.Body))
{
await writer.WriteLineAsync("Done!");
return;
}
}

Hopefully this helps someone not waste as much time as I did.

In my case, I upgraded an Azure Function Project from .NETCoreApp v3.1 to .NET6.0, and getting this error.

So what I did was change HttpMessage for IActionResult.

My code was like this:

[FunctionName("SayHello")]
public static async Task<HttpResponseMessage> RunAsync([HttpTrigger(AuthorizationLevel.Function, "get", Route = "SayHello")]HttpRequestMessage req, ILogger log)
{
string name =  await req.Content.ReadAsAsync<string>();
return req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}

And, now:

[FunctionName("SayHello")]
public static async Task<IActionResult> RunAsync([HttpTrigger(AuthorizationLevel.Function, "get", Route = "SayHello")]HttpRequestMessage req, ILogger log)
{
string name =  await req.Content.ReadAsAsync<string>();
return new OkObjectResult("Hello " + name);
}