Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autofac dispose order

Tags:

c#

.net

autofac

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()
like image 734
Paranoid Avatar asked Oct 05 '16 09:10

Paranoid


People also ask

Does Autofac call 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.

How do I get Autofac containers?

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).

What is lifetime scope in Autofac?

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.

What is Autofac C#?

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.


1 Answers

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;
}
like image 55
Cyril Durand Avatar answered Oct 12 '22 22:10

Cyril Durand