Closed stigc closed 2 years ago
ThreadScopedLifestyle
is a legacy feature. It exists for application types where asynchronous scoping is not supported. This typically means platforms that only support .NET Standard 1.0, 1.1, or 1.2.
As there is no noticable performance penalty between ThreadScopedLifestyle
and AsyncScopedLifestyle
, and considering that you can safely use AsyncScopedLifestyle
for single-threaded operations as well, our advise it to always use AsyncScopedLifestyle
as your default scoped lifestyle.
ThreadScopedLifestyle
will likely be removed from a future version of Simple Injector (e.g. 6.0 or 7.0).
- If we had only 1 call to GetInstance, ThreadScopedLifestyle would be fine, right? (assuming there is no async/await code before the GetInstance)
In theory, yes. But consider that its possible to have calls to GetInstance
from deep inside the call graph at any point in time, for instance when doing dispatching of messages to (a list of) registered service(s). This makes it quite tricky to use the ThreadScopedLifestyle
for applications that apply asynchronous programming techniques.
Thanks, you explained it well enough, we well change to AsyncScopedLifestyle. The documentation is not that clear :)
@dotnetjunkie If we had only 1 call to GetInstance, ThreadScopedLifestyle would be fine, right? (assuming there is no async/await code before the GetInstance) In theory, yes. But consider that its possible to have calls to GetInstance from deep inside the call graph at any point in time, for instance when doing dispatching of messages to (a list of) registered service(s). This makes it quite tricky to use the ThreadScopedLifestyle for applications that apply asynchronous programming techniques.
I was assuming that the container is not accessible later in the code. Also there must not be any async/await code between the GetInstance and the dispose (when the using is ending), right?
I was assuming that the container is not accessible later in the code. Also there must not be any async/await code between the GetInstance and the dispose (when the using is ending), right?
Consider the following code:
using (ThreadScopedLifestyle.BeginScope(container))
{
var controller = container.GetInstance<OrdersController>();
await controller.ShipOrder(id);
}
This code might imply that object resolution always takes place that the thread that started the ThreadScopedLifestyle
, but that might not be the case. Consider the following implementation for OrdersController
:
public record OrdersController(IMediator mediator) : Controller
{
public Task ShipOrder(Guid id)
{
await SomeValidation();
await this.mediator.Dispatch(new ShipOrderCommand(id));
}
}
A Mediator is an object that would dispatch to dynamically loaded implementations, for instance:
public record SimpleInjectorMediator(Container container) : IMediator
{
public Task Dispatch<TCommand>(TCommand command)
{
var handler = this.container.GetInstance<ICommandHandler<TCommand>>();
await handler.Handle(command);
}
}
While the IMediator
interface might be defined in a core library of the application, its SimpleInjectorMediator
implementation would be implemented in the Composition Root which allows it access to the DI Container. The SimpleInjectorMediator
only starts resolving a command handler after its Dispatch
function is called.
If we remove the OrdersController
and SimpleInjectorMediator
and flattern everything down to a single method, you can more easily see that even the seemingly innocent first example, calls GetInstance
after calling await
:
using (ThreadScopedLifestyle.BeginScope(container))
{
// GetInstance before await
var controller = container.GetInstance<OrdersController>();
await controller.SomeValidation();
// GetInstance *after* await
var handler = container.GetInstance<ICommandHandler<ShipOrderCommand>>();
await handler.Handle(new ShipOrderCommand(id));
}
Thanks @dotnetjunkie this is apricated. I see there are a lot of pitfalls, when also using the more advanced SimpleInjector technics.
Asynchronous programming is one big pitfall in itself. Unfortunately, there's no way we can avoid it nowadays.
I am a little confused when to use Thread Scope instead of Async Scoped.
This is the AsyncScopedLifestyle example from the documentation