I am trying to understand exactly what happens with AddScoped after a conversation with a colleague. I have always interpreted that AddScoped create a new instance of the manager class in a service for each request. My colleague is telling me that even if there is a new instance of the class it is still not thread safe because those instances could be in the same thread.
Interface
public interface IManager
{
string AuthValue { get; set; }
Task<bool> DoSomethingAsync();
Task<bool> DoSomethingElseAsync();
}
Manager
public class Manager : IManager
{
protected string _AuthValue;
string IManager.AuthValue { get => _AuthValue; set => _AuthValue = value; }
public async Task<bool> DoSomethingAsync()
{
bool result = false;
///somecode that could take a few seconds to run
if (_AuthValue == "123")
{
result = true;
}
return result;
}
public async Task<bool> DoSomethingElseAsync()
{
bool result = false;
///somecode that could take a few seconds to run
if (_AuthValue == "456")
{
result = true;
}
return result;
}
}
program.cs
builder.Services.AddScoped<IManager, Manager>();
var app = builder.Build();
Service
private readonly ILogger<MyService> _logger;
private IManager _manager;
public MyService(ILogger<MyService> logger, IManager manager)
{
_logger = logger;
_manager = manager;
}
public override Task<bool> SayHello(HelloRequest request, ServerCallContext context)
{
_manager.AuthValue = context.RequestHeaders.GetValue("Auth");
_manager.DoSomethingAsync();
}
public override Task<bool> SayHelloAgain(HelloRequest request, ServerCallContext context)
{
_manager.AuthValue = context.RequestHeaders.GetValue("Auth");
_manager.DoSomethingElseAsync();
}
I am not advocating for this way of doing things, but I just want to understand better.
My college is saying that when multiple concurrent requests come in because the scoped could be using the same thread there is a chance that when a request comes in for “SayHello” and begins executing “DoSomethingAsync” if a new request comes in for “SayHelloAgain” with a different “AuthValue”, while “SayHello” in first request is still running, that it will potentially update the AuthValue for the first request because the service is running on the same thread.
I have always understood that using DI as scoped meant that each request spun up a new instance of that manager class to be used so things like this could not happen. If I am wrong, can it please be explained how adding a class as scoped is meant to be used.
I have tried searching multiple times even making past page 1 of results. Everything seems to read as my understanding is right but there has not been anything specific enough to my scenario to confirm.
- What Happens When the Same Thread Serves a Different Request?
Thread Reuse: ASP.NET Core uses a thread pool to handle incoming requests. Threads are reused to improve performance. This means that the same thread might handle multiple requests over its lifetime.
Scoped Service Lifetime: The lifetime of a scoped service is tied to the DI scope, not the thread. Each HTTP request creates a new DI scope, and all scoped services are resolved within this scope.
Isolation Between Requests: Even if the same thread handles multiple requests, the scoped services from one request are not carried over to the next. Each request has its own set of scoped service instances.
- Implications:
No Cross-Request Leakage: You don’t have to worry about scoped services leaking data or state between requests, even on the same thread.
Thread Safety: Since each request gets its own instance of scoped services, you avoid concurrency issues related to shared state.