What is AsyncServiceScope and When to use it?

To better understand the subject first, we need to know about IAsyncDisposable!

One of the features that added to .NET Core 3.0 was IAsyncDisposable, which lets you run async code when disposing of resources, helping to avoid deadlocks. Before we had only an IDisposable interface with a void method called Dispose, so obviously we couldn't use async/await, We had something like this:

public class Phone : IPhone, IDisposable
{
    public void Dispose()
    {
        // Free resources
    }
}

But here is the async version of it:

public class Phone : IPhone, IAsyncDisposable
{
    public ValueTask DisposeAsync()
    {
        // Free resources asynchronously
    }
}

The DisposeAsync method allows us to clean up resources asynchronously.

So what is the issue then?! As maybe you already thought, the tricky part is that any code cleaning up IDisposable objects now also needs to handle IAsyncDisposable objects. This means those code paths must also be async, due to the nature of async/await.

One of the places that needs to change is the DependencyInjection container, so we all know DI will resolve services and then dispose of them based on their lifetime. So normally DI will call either Dispose or DisposeAsync method based on class implementation.

So given that IPhone interface, assuming we inject it as Scoped service:

builder.Services.AddScoped<IPhone, Phone>();

//------------Endpoint-----------
app.MapGet("/phones", ([FromServices] IPhone phone) => { /* code */});

Every time we call the /phones API, DI will create a new scope and instantiate IPhone implementation and after the request is finished will dispose the object (by calling DisposeAsync).

All good, no issues, right?

The problem comes into the picture when we want to create a scope manually by using IServiceProvider or IServiceScopeFactory, let's back to that API again:

app.MapGet("/phones", ([FromServices] IServiceScopeFactory scopeFactory) =>
{
    using var scope = scopeFactory.CreateScope();

    var phone = scope.ServiceProvider.GetRequiredService<IPhone>();

    /* Code */
})

Basically, there we create a scope and then get a new instance of IPhone implementation which is Phone. If we call the API you will get this exception:

System.InvalidOperationException
  HResult=0x80131509
  Message= 'Phone' type only implements IAsyncDisposable. Use DisposeAsync to dispose the container.
  Source=Microsoft.Extensions.DependencyInjection
  StackTrace:
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.Dispose()
   at Program.<>c.<<Main>$>b__0_3(IServiceScopeFactory scopeFactory) in C:\Users\******\Program.cs:line 114
   at Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.Invoke(HttpContext context)

It is complaining that Phone is implemented the IAsyncDispose but we are calling the Dispose method not DisposeAsync, actually calling dispose method will be done behind the scenes when using is done and tries to dispose the created scope and all created instances, here AsyncServiceScope comes into the picture!

AsyncServiceScope allows DI to call the DisposeAsync method and basically means allows DI to dispose objects asynchronously! Then our above code should be changed as below:

app.MapGet("/phones", async ([FromServices] IServiceScopeFactory scopeFactory) =>
{
    await using var scope = scopeFactory.CreateAsyncScope();

    var phone = scope.ServiceProvider.GetRequiredService<IPhone>();

    /* Code */
});

Now DI is happy and we are happy as everything works!

Code Simple!

1
An error has occurred. This application may no longer respond until reloaded. Reload x