3

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

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
Daniël Camps
  • 1,737
  • 1
  • 22
  • 33

1 Answers1

5

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.

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
  • Thank you for the elaborate answer. Your explanation with regard to accessing a disposed object in a new thread after the using block has been executed makes sense. I'm still quite unsure however of how to proceed with regard to starting a new thread with autofac, using DI. I know I can get the code working without multithreading, but I really do need both actions to happen in parallel. I therefore tried to inject the newly instantiated lifetime scope into the new thread as a parameter, hoping to avoid the aforementioned issue, but this still does not work unfortunately – Daniël Camps Apr 19 '19 at 13:40
  • What is the other thing that must happen simultaneously? Is it an I/O operation, like a database query or a web request? If so, perhaps something asynchronous [like this](https://stackoverflow.com/questions/38634376/running-async-methods-in-parallel) would help. It's not using a different thread. A single thread can start one operation, leave it to finish, start another operation, and then resume after both have finished. – Scott Hannen Apr 19 '19 at 18:10
  • I need to check the database for a user connected to the action. Subsequently, I have to check the active directory for the user's email address. Then, I have to send an email message to an SMTP server. I prefer not to make a user wait for these actions before knowing their save action was successful – Daniël Camps Apr 24 '19 at 06:04
  • None of those things should require resolving dependencies from that scope. What you're doing sounds similar to raising a domain event. You could do that or instead of sending the email immediately, place something on a queue so that those separate actions don't take place synchronously. – Scott Hannen Apr 24 '19 at 12:06
  • I will look into domain events, thank you for pointing me in that direction. – Daniël Camps Apr 25 '19 at 11:42
  • To overgeneralize, think of it as raising an event that just says something happened, and it includes some information about what happened. It's not specific about what to do when that thing happens. That gives you flexibility to do all sorts of stuff downstream, and the class that raises the event doesn't even need to know about it. That keeps everything separate and easier to manage. – Scott Hannen Apr 25 '19 at 12:03