在与同事交谈后,我试图准确了解 AddScoped 发生了什么。 我一直认为 AddScoped 在服务中为每个请求创建一个新的管理器类实例。 我的同事告诉我,即使有该类的新实例,它仍然不是线程安全的,因为这些实例可能位于同一个线程中。
界面
public interface IManager
{
string AuthValue { get; set; }
Task<bool> DoSomethingAsync();
Task<bool> DoSomethingElseAsync();
}
经理
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;
}
}
程序.cs
builder.Services.AddScoped<IManager, Manager>();
var app = builder.Build();
服务
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();
}
我并不是提倡这种做法,只是想更好地理解。
我的大学说,当多个并发请求进来时,因为作用域可能使用相同的线程,当一个请求进入“SayHello”并开始执行“DoSomethingAsync”时,如果有一个新请求进入“SayHelloAgain”,则有可能使用不同的“AuthValue”,而第一个请求中的“SayHello”仍在运行,它可能会更新第一个请求的 AuthValue,因为服务正在同一线程上运行。
我一直明白,使用 DI 作为作用域意味着每个请求都会启动该管理器类的一个新实例以供使用,因此这样的事情不会发生。 如果我错了,能否解释一下如何使用添加类作为作用域。
我尝试过多次搜索,甚至搜索到了结果的第一页。 一切似乎都按照我的理解是正确的,但没有任何具体的内容足以证实我的情况。
线程重用:ASP.NET Core 使用线程池来处理传入请求。线程被重用以提高性能。这意味着同一个线程在其生命周期内可能会处理多个请求。
作用域服务生命周期:作用域服务的生命周期与 DI 作用域相关,而不是与线程相关。每个 HTTP 请求都会创建一个新的 DI 范围,所有范围内的服务都在该范围内解析。
请求之间的隔离:即使同一线程处理多个请求,一个请求中的范围服务也不会转移到下一个请求。每个请求都有自己的一组范围内的服务实例。
无跨请求泄漏:您不必担心作用域服务在请求之间泄漏数据或状态,即使在同一线程上也是如此。
线程安全:由于每个请求都有自己的作用域服务实例,因此您可以避免与共享状态相关的并发问题。