113

ASP.NET core server, AllowSynchronousIO is set to false

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

In the action, it outputs a JsonResult

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

        return this.Json(dic);
    }

And it ends with an exception

System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.

Can't I return a JsonResult with AllowSynchronousIO=false ?

Enlico
  • 23,259
  • 6
  • 48
  • 102
Mr.Wang from Next Door
  • 13,670
  • 12
  • 64
  • 97

11 Answers11

157

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;
    });
}
Eilon
  • 25,582
  • 3
  • 84
  • 102
hightech
  • 1,530
  • 1
  • 10
  • 8
32

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();
})
Mark Lagendijk
  • 6,247
  • 2
  • 36
  • 24
  • Voting for this answer, as it enables only for certain requests and keep the recommended setting for the rest. It also extends the source issue description by full sample code. – Jan Zahradník Mar 21 '23 at 07:09
  • CAUTION: You have to be very careful when updating the context features to avoid any race conditions causing the issue of https://github.com/dotnet/aspnetcore/issues/42040, basically unless you are single threaded (i.e. always awaiting any async tasks) you should prefer to synchronize all access to the request by using a lock statement etc. – yoel halb Jun 06 '23 at 18:56
26

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.

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
  • 4
    Yep - sure saved some time here! Thank you :) – bambam Apr 14 '22 at 17:19
  • 1
    The error message is so dumb. In my code, it throw an error at `await writer.WriteAsync` call and the error tells me `Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.`. Thankfully I found this answer. – Luke Vo Feb 06 '23 at 08:06
  • 1
    Good for you. I have the exact same problem with ZipArchive streams, that unfortunately do not implement `iAsynDisposable` :( https://github.com/dotnet/runtime/issues/1560 – Alex from Jitbit Mar 18 '23 at 21:55
10

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;
reven
  • 330
  • 4
  • 14
10

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();
}
T-moty
  • 2,679
  • 1
  • 26
  • 31
4

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;
            });
Pavan Ambhure
  • 49
  • 1
  • 5
3

I had this problem when writing ZipArchive into Response.Body which throws the exact same error, because ZipArchive does not implement IAsynDisposable

using (var z = new ZipArchive(Response.Body, ZipArchiveMode.Create, true))
{
    //fill the zip archive
} //exception thrown here

The workaorund is to write into Response.BodyWriter.AsStream()

using (var z = new ZipArchive(Response.BodyWriter.AsStream(), ZipArchiveMode.Create, true))
{
    //fill the zip archive
}
Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149
1

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.

TheBlueSky
  • 5,526
  • 7
  • 35
  • 65
1

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;
    }
}
0

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.

Artur Krajewski
  • 571
  • 1
  • 4
  • 18
0

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);
}
krthx
  • 1