I started using Autofac for DI in a project of mine and there's one thing about it I cannot get clear from documentation / googling (maybe I'm missing something).
According to documentation, when a container (or a LifetimeScope) is disposed, Autofac automatically disposes all Disposable instances resolved in this container / scope.
The question is: are those instances disposed in some specific order (which is officially guaranteed)?
It seems natural to expect that if some Client
instance is injected with a reference to a Service
instance, then the Client
should be disposed before the Service
. (Let's assume the dependency graph has no circular references, and such order can be correctly defined).
If this is not true, and the dependency graph nodes may be disposed in arbitrary order, it means that I have to take some additional precautions in component implementation, so that every component behaves correctly when some of its dependencies suddenly turn out dead. This makes life harder.
I made a set of simple tests, and in (almost) all scenarios the order of disposal is indeed the "natural" one.
I also skimmed through the Autofac sources and found out that all auto-disposable instances are stored internally on a stack, and are disposed in the pop() order (i.e. reverse to oder of store), which makes me believe that some specific dispose order is actually enforced.
Can someone comment on this? Thank you.
EDIT A violation of the "natural" dispose order happens when I'm trying to do property injection using PropertiesAutowired()
(which works by hooking OnActivated
event). The following code:
class Service : IDisposable
{
public Service()
{
Console.WriteLine("Service.ctor()");
}
public void Dispose()
{
Console.WriteLine("Service.Dispose()");
}
}
class Client : IDisposable
{
public Service Service { get; set; }
public Client()
{
Console.WriteLine("Client.ctor()");
}
public void Dispose()
{
Console.WriteLine("Client.Dispose()");
}
}
class Program
{
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<Service>();
builder.RegisterType<Client>().PropertiesAutowired();
using (var container = builder.Build())
{
var clientProp = container.Resolve<Client>();
}
}
}
produces the following output:
Client.ctor()
Service.ctor()
Service.Dispose()
Client.Dispose()
Autofac calls Dispose for all instances of components implementing IDisposable once their parent lifetime scope ends. You don't need to do any additional work here.
From Visual Studio, you can get it via NuGet. The package name is Autofac. Alternatively, the NuGet package can be downloaded from the GitHub repository (https://github.com/autofac/Autofac/releases).
The concept of a lifetime scope in Autofac combines these two notions. Effectively, a lifetime scope equates with a unit of work in your application. A unit of work might begin a lifetime scope at the start, then services required for that unit of work get resolved from a lifetime scope.
Autofac is an addictive IoC container for . NET. It manages the dependencies between classes so that applications stay easy to change as they grow in size and complexity. This is achieved by treating regular . NET classes as components.
You are right, each disposable component are disposed the reverse order they have been created.
Each ILifetimeScope
have a IDisposer
(disposer.cs) instance which tracks instances of all IDisposable
objects for its scope.
/// <summary>
/// Provided on an object that will dispose of other objects when it is
/// itself disposed.
/// </summary>
public interface IDisposer : IDisposable
{
/// <summary>
/// Adds an object to the disposer. When the disposer is
/// disposed, so will the object be.
/// </summary>
/// <param name="instance">The instance.</param>
void AddInstanceForDisposal(IDisposable instance);
}
Default implementation of IDisposer
(disposer.cs) uses a Stack<IDisposable>
, the Dispose
method is called by the Dispose
method of the ILifetimeScope
and 'pop' the stack.
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing">
/// <c>true</c> to release both managed and unmanaged resources;
/// <c>false</c> to release only unmanaged resources.
/// </param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
lock (_synchRoot)
{
while (_items.Count > 0)
{
var item = _items.Pop();
item.Dispose();
}
_items = null;
}
}
base.Dispose(disposing);
}
The AddInstanceForDisposal
is called just after the instanciation of the component. See the Activate
method of the InstanceLookup
(InstanceLookup.cs]
private object Activate(IEnumerable<Parameter> parameters)
{
ComponentRegistration.RaisePreparing(this, ref parameters);
try
{
_newInstance = ComponentRegistration.Activator.ActivateInstance(this, parameters);
}
catch (Exception ex)
{
throw new DependencyResolutionException(ex);
}
if (ComponentRegistration.Ownership == InstanceOwnership.OwnedByLifetimeScope)
{
// The fact this adds instances for disposal agnostic of the activator is
// important. The ProvidedInstanceActivator will NOT dispose of the provided
// instance once the instance has been activated - assuming that it will be
// done during the lifetime scope's Disposer executing.
var instanceAsDisposable = _newInstance as IDisposable;
if (instanceAsDisposable != null)
_activationScope.Disposer.AddInstanceForDisposal(instanceAsDisposable);
}
ComponentRegistration.RaiseActivating(this, parameters, ref _newInstance);
return _newInstance;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With