Reputation: 81
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()
Upvotes: 8
Views: 2337
Reputation: 16192
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;
}
Upvotes: 5