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