-
Notifications
You must be signed in to change notification settings - Fork 4.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make CreateScope return ServiceScope that also implements IAsyncDisposable #43970
Comments
Tagging subscribers to this area: @eerhardt, @maryamariyan |
Is this a valuable enough change to break these implementations? cc @davidfowl |
It is not. |
As |
Can you propose the API? It would need to be an extension method (with a different name than CreateScope) that wraps the existing scope and does the test for IAsyncDisposable. |
Sure, naming is the hardest 😄. Sometimes I wish one could just slam on the good old Alternative 1. Introduce new interfacenamespace Microsoft.Extensions.DependencyInjection
{
public interface IAsyncDisposableServiceScope : IServiceScope, IAsyncDisposable { }
public static class ServiceProviderScopeExtensions
{
public static IAsyncDisposableServiceScope CreateServiceScope(this IServiceProvider provider) { }
}
}
No need to wrap in most cases if we let Line 12 in cb873bc
Alternative 2. Introduce new classThis is similar to what's happening when creating namespace Microsoft.Extensions.DependencyInjection
{
public abstract class ServiceScope : IServiceScope, IAsyncDisposable
{
public abstract IServiceProvider ServiceProvider { get; }
public abstract void Dispose();
public abstract ValueTask DisposeAsync();
}
public static class ServiceProviderScopeExtensions
{
public static ServiceScope CreateServiceScope(this IServiceProvider provider) { }
}
} Same here, no need to wrap in most cases if we let |
Alternative 1 would return null if the underlying scope doesn't implement the interface? Remember our container is a single implementation, there are many others that would need to implement this new interface. That means it would return null if the implementation doesn't? The latter is fine but requires an additional allocation for those providers that don't derive from our new base class. |
Alternative 1 would wrap the scope in an internal class (implenting the interface) instead of returning null, thus would also require an additional allocation. So both alternatives are similar. Question. If the new interface (or class) and extension method would reside in Would it make sense to add the new interface and extensions method to |
This keeps coming up so I think we should go with a modified version of alternative 2: public static class ServiceScopeExtensions
{
public static AsyncServiceScope CreateAsyncServiceScope(this IServiceProvider serviceProvider)
{
return new AsyncServiceScope(serviceProvider.CreateScope());
}
}
public struct AsyncServiceScope : IServiceScope, IAsyncDisposable
{
private readonly IServiceScope _serviceScope;
public AsyncServiceScope(IServiceScope serviceScope)
{
_serviceScope = serviceScope;
}
public IServiceProvider ServiceProvider => _serviceScope.ServiceProvider;
public void Dispose()
{
_serviceScope.Dispose();
}
public ValueTask DisposeAsync()
{
if (_serviceScope is IAsyncDisposable ad)
{
return ad.DisposeAsync();
}
_serviceScope.Dispose();
return ValueTask.CompletedTask;
}
} @maryamariyan we should take this to one API review. The change is tiny but we need to agree on the name. |
public partial class ServiceProviderServiceExtensions
{
public static AsyncServiceScope CreateAsyncScope(this IServiceProvider serviceProvider)
{
return new AsyncServiceScope(serviceProvider.CreateScope());
}
}
public struct AsyncServiceScope : IServiceScope, IAsyncDisposable
{
private readonly IServiceScope _serviceScope;
public AsyncServiceScope(IServiceScope serviceScope)
{
_serviceScope = serviceScope;
}
public IServiceProvider ServiceProvider => _serviceScope.ServiceProvider;
public void Dispose()
{
_serviceScope.Dispose();
}
public ValueTask DisposeAsync()
{
if (_serviceScope is IAsyncDisposable ad)
{
return ad.DisposeAsync();
}
_serviceScope.Dispose();
return ValueTask.CompletedTask;
}
} |
@bjorkstromm Are you interested in sending a pull request for this API? |
Sure! It would be my pleasure. |
…isposable. - Introduces a new type AsyncServiceScope that implements IServiceScope and IAsyncDisposable. The type just wraps an existing IServiceScope instance, which it tries to cast it to an IAsyncDisposable when DisposeAsync is called. - Adds netstandard2.1 target to avoid bringing in System.Threading.Tasks.Extensions and Microsoft.Bcl.AsyncInterfaces if not needed. - Fixes dotnet#43970
…isposable. (#51840) * Adds extension method to create service scope that implements IAsyncDisposable. - Introduces a new type AsyncServiceScope that implements IServiceScope and IAsyncDisposable. The type just wraps an existing IServiceScope instance, which it tries to cast it to an IAsyncDisposable when DisposeAsync is called. - Adds netstandard2.1 target to avoid bringing in System.Threading.Tasks.Extensions and Microsoft.Bcl.AsyncInterfaces if not needed. - Fixes #43970 * Make AsyncServiceScope readonly Co-authored-by: David Fowler <davidfowl@gmail.com> * Use null-coalescing for null checking and argument exception. Co-authored-by: David Fowler <davidfowl@gmail.com> * Make AsyncServiceScope readonly in reference source. * Adds tests for AsyncServiceScope and CreateAsyncScope extension method. * Merge generated ref source. * Document why 'default' is used instead of 'ValueTask.CompletedTask' * Remove unnecessary casts to IDisposable and IAsyncDisposable in tests. Co-authored-by: David Fowler <davidfowl@gmail.com>
Background and Motivation
IAsyncDisposable
support in DI was added a while ago (see dotnet/extensions#426), but then it was agreed to only add interface implementation toServiceProvider
. I didn't find any tracking issue on implementing it also on scopes.Currently, the following code will throw exception:
Workaround today is to cast the returned scope to
IAsyncDisposable
, because the concrete implementation in DI implements that.Proposed API
IServiceScope
implementsIAsyncDisposable
Usage Examples
Changing this would allow us to use
DisposeAsync
on the returned scope.Alternative Designs
Create a new interface
IAsyncDisposableServiceScope
(needs better naming), which implementsIServiceScope
andIAsyncDisposable
and letIServiceScopeFactory.CreateScope()
return that.Risks
Will probably break some 3rd party implementations of
Microsoft.Extensions.DependencyInjection.Abstractions
and consumers.The text was updated successfully, but these errors were encountered: