Search code examples
c#.netdependency-injectionautofac

Autofac Creating Child Scope for New Thread not working as expected "Instances cannot be resolved and nested lifetimes cannot be created..."


When someone calls the URL "/index/5", I want to return a view, and in parallel I want to start a new thread in which I want to figure out with some DB calls and business logic whether I should send someone else a notification. Here is a simplified representation of my setup, which gives an error.

How can I get my child scope to work in a parallel thread?

App Startup

var builder = new ContainerBuilder();
builder.RegisterType<MyRepository>().As<IMyRepository();
builder.RegisterType<Entities>().As<Entities>().InstancePerRequest();
var container = builder.Build();

Controller

    private readonly IMyRepository _myRepository ;

    public MyController(
        IMyRepository myRepository
       )
    {
        _myRepository = myRepository;
    }

    public async Task<ActionResult> Index(int id)
    {
        _myRepository.DoSomething(id);

        return View();
    }

Repository:

private ILifetimeScope _lifeTimeScopeChild = null;

public void DoSomething(int id){
            //start new thread with child scope
            using(var threadLifeTime = AutofacDependencyResolver.Current.ApplicationContainer.BeginLifetimeScope())
            {
                _lifeTimeScopeChild = threadLifeTime;
                 Thread t = new Thread(new ParameterizedThreadStart(MySeparateThread));
                 t.Start(id);  
            }
        }

        private void MySeparateThread(object id) {
                    var _entities = _lifeTimeScopeChild.Resolve<Entities>(); //IT CRASHES HERE
        }

Error:

Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed.

What I'm trying to accomplish: https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#thread-scope


Solution

  • The key part of this isn't the first part of the error message, but the last part:

    it has already been disposed.

    using(var threadLifeTime = AutofacDependencyResolver.Current.ApplicationContainer.BeginLifetimeScope())
    {
        _lifeTimeScopeChild = threadLifeTime;
        Thread t = new Thread(new ParameterizedThreadStart(MySeparateThread));
        t.Start(id);  
    }
    

    threadLifeTime is created in the declaration of your using block. At the end of that block it gets disposed. That's the sole purpose of a using block. There's an assumption built in that it's okay for the object to get disposed at that point. You're done with it.

    You're also creating a separate thread and passing threadLifeTime to it. The part of the code where that's happening isn't shown, but that's what's happening. You may not be passing it explicitly, but it's referenced somewhere in ParameterizedThreadStart and MySeparateThread.

    That thread goes on executing separately from the method that originally called it. So immediately after you hand over that object, it gets disposed. Whatever that thread is doing, it's trying to do it with a disposed object. That's the error.

    Normally at the end of the using block the variable (threadLifeTime) would go out of scope. There would be no references to it, so it wouldn't matter if it's disposed. But now there's a reference to it, which is a problem since it's a reference to something that has been disposed.

    The short-term solution is not to create a separate thread. If there is a correct answer to this that involves multithreading, it's more complicated and beyond the scope of this answer. A good rule of thumb is to get code working without multithreading first, then add it if you need it. Otherwise you don't know if the problem is the multithreading or something else.

    Another concern is this:

    _lifeTimeScopeChild = threadLifeTime;
    

    It indicates that not only is it getting passed to some other thread, it's also being assigned to a field within the class. That's also a problem for the exact same reason. The reference you're assigning to that field will still exist even after the object is disposed. If anything tries to use _lifeTimeScopeChild after this using block completes it's going to get the same error.

    The question to answer is "Does my method need this object or does my class need this object?"

    If your method needs it then declare and use it within the method, but don't allow any references to it that you can't control to "escape" from the method. If you dispose it then you'll break anything else that tries to use it. And if you don't dispose it then, well, it doesn't get disposed when it should.

    If your class needs it then consider either creating it when the class is created or using lazy instantiation to create it when you need it. (I'd start with the former.) Then make your class implement IDisposable, and when your class gets disposed, that's when you dispose of any disposable resources used by your class.