开发者

Autofac reporting circular dependencies that don't exist

开发者 https://www.devze.com 2023-01-25 00:45 出处:网络
I recently added Autofac to an large, existing application to manage DI. In the process, I replaced singletons with a single instance managed by the container that gets injected into the dependent\'s

I recently added Autofac to an large, existing application to manage DI.

In the process, I replaced singletons with a single instance managed by the container that gets injected into the dependent's constructor. In some cases, however, circular dependencies had to be broken. The easiest way that I found to do this was to take advantage of the OnActivated event. We intend to modify these types to eliminate the circular dependencies, but the risk of changing them now is too great.

To the types involved in circular dependencies, I added a method called ResolveCircularDependencies (this makes it obvious that this method is only to be used temporarily and for the purpose of resolving these cycles). This method gets called in the OnActivated event.

So my code now looks like this:

public class ServiceA
{
    private ServiceB otherService;

    public ServiceA()
    {
        ...
    }

    public void ResolveCircularDependencies(ServiceB other)
    {
        this.otherService = other;
    }

    public void SomeMethod()
    {
        ...
        this.otherService.SomeMethod();
        ...
    }
}

public class ServiceB
{
    private ServiceA otherService;

    public ServiceB()
    {
        ...
    }

    public void ResolveCircularDependencies(ServiceA other)
    {
        this.otherService = other;
    }

    public void SomeMethod()
    {
        ...
        this.otherService.SomeMethod();
        ...
    }
}

These types are registered in an Autofac module, with the Load method as follows:

public override void Load(ContainerBuilder builder)
{
    builder
        .Register(ctx => new ServiceA())
        .OnActivated(e => e.Instance.ResolveCircularDependences(e.Context.Resolve<ServiceB>()));

    builder
        .Register(ctx => new ServiceB())
        .OnActivated(e => e.Instance.ResolveCircularDependences(e.Context.Resolve<ServiceA>()));
}

This appears to work correctly in most cases. However, we are randomly seeing that Autofac believes it has found a circular dependency and returns the following stack trace with the exception:

at Autofac.Core.Resolving.CircularDependencyDetector.CheckForCircularDependency(IComponentRegistration registration, Stack`1 activationStack, Int32 callDepth)
at Autofac.Core.Resolving.ResolveOperation.Resolve(ISharingLifetimeScope activationScope, IComponentRegistration registration, IEnumerable`1 parameters)
at Autofac.Core.Resolving.ComponentActivation.Resolve(IComponentRegistration registration, IEnumerable`1 parameters)
at Autofac.ResolutionExtensions.TryResolve(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
at Autofac.ResolutionExtensions.Resolve(IComponentContext context, Service service, IEnumerable`1 parameters)
at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context, IEnumerable`1 parameters)
at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context)
at DomainObjectFactory.Resolve[T]()
at DomainObjectFactory.BuildMyObject()

We also randomly see the following error:

at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
at System.Collections.Generic.Stack`1.Enumerator.MoveNext()
at System.Linq.Enumerable.Count[TSource](IEnumerable`1 source, Func`2 predicate)
at Autofac.Core.Resolving.CircularDependencyDetector.IsCircularDependency(IComponentRegistration registration, Stack`1 activationStack)
at Autofac.Core.Resolving.CircularDependencyDetector.CheckForCircularDependency(IComponentRegistration registration, Stack`1 activationStack, Int32 callDepth)
at Autofac.Core.Resolving.ResolveOperation.Resolve(ISharingLifetimeScope activationScope, IComponentRegistration registration, IEnumerable`1 parameters)
at Autofac.Core.Resolving.ComponentActivation.Resolve(IComponentRegistration registration, IEnumerable`1 parameters)
at Autofac.ResolutionExtensions.TryResolve(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
at Autofac.ResolutionExtensions.Resolve(IComponentContext context, Service service, IEnumerable`1 parameters)
at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context, IEnumerable`1 parameters)
at Autofac.Re开发者_Python百科solutionExtensions.Resolve[TService](IComponentContext context)
at DomainObjectFactory.Resolve[T]()
at DomainObjectFactory.BuildMyObject()

These are occurring well after all registrations are completed (which occurs on a single thread at application startup). Calls to BuildMyObject method could be made simultaneously on separate threads. This, however, appears to acceptable according to the Autofac wiki.

I have reviewed the complete dependency trees of ServiceA and ServiceB and there are no cycles in the object trees.

Has anyone seen this behavior? What is the resolution?

We are using Autofac 2.3.2.632-NET35 as released for download here.


The IComponentContext that your DomainObjectFactory is calling into appears to be a temporary one that was created during a single resolve operation, e.g. the c parameter in something like:

builder.Register(c => new DomainObjectFactory(c))

These aren't thread-safe; the correct code would be:

builder.Register(c => new DomainObjectFactory(c.Resolve<IComponentContext>())

It is a nasty gotcha that appears now and then, but is usually detectable because once c is disposed the Resolve() call would throw the telltale ObjectDisposedException. I'll make a note on the concurrency page you linked to.

0

精彩评论

暂无评论...
验证码 换一张
取 消