Asp.Net 每个请求的核心异步惰性初始化
Asp.Net Core async lazy initialization per request
对于我们的多租户 Asp.Net Core 2.2 Web 应用程序中的许多端点,我们需要根据每个请求(使用异步数据库调用)从数据库中获取一些租户信息,然后重用该信息根据需要从请求管道中的各个点获取信息,而不必每次都调用数据库。也就是说,我们想要基于每个请求的惰性异步 属性。
根据我阅读的各种文档,scoped 服务的实例一次只能由一个线程(处理请求的线程)访问。所以,我相信这意味着我应该能够安全地执行以下操作:
// Registered as scoped in the DI container
public class TenantInfoRetriever : ITenantInfoRetriever
{
private TenantInfo _tenantInfo;
private bool _initialized;
public async Task<TenantInfo> GetAsync()
{
// This code is obviously not thread safe, which is why it's important that no two threads are running here at the same time.
if (!_initialized)
{
_tenantInfo = await GetTenantInfoAsync(); // this might actually legitimately return null, hence the need for the separate property "_initialized"
_initialized = true;
}
return _tenantInfo;
}
private async Task<TenantInfo> GetTenantInfoAsync()
{
return await DoDatabaseCallToGetTenantInfo(); // of course this would use an injected EfContext instance (scoped service)
}
}
据我所知,这应该可行,因为作用域服务不需要是线程安全的。
我的问题:我的假设是否正确,或者我是否遗漏了一些重要的东西?
Based on the various docs I've read, instances of scoped services are only ever accessed by one thread at a time (the thread processing the request).
这不完全正确。当您等待一个操作时,线程返回到线程池,当异步操作恢复时,线程池中的一个空闲线程将被拾取。
它不需要启动它的完全相同的线程。
但是,是的,不会有两个线程同时访问它,当它的作用域是从ASP.NET核心端。
当然,你自己的代码也要有所防范。
如果您同时启动两个任务/线程并 运行 它们,那么它仍然有可能同时被访问。
如果您担心它会被访问,请仔细检查锁定模式(使用可变字段)或锁定互斥锁(不是您修改的值的对象)或使用 AsyncLazy<T>
,即来自this blog post.
示例(来自 https://help.semmle.com/wiki/display/CSHARP/Double-checked+lock+is+not+thread-safe)
示例 1:
string name;
public string Name
{
get
{
lock (mutex) // GOOD: Thread-safe
{
if (name == null)
name = LoadNameFromDatabase();
return name;
}
}
}
示例 2:
volatile string name; // GOOD: Thread-safe
public string Name
{
get
{
if (name == null)
{
lock (mutex)
{
if (name == null)
name = LoadNameFromDatabase();
}
}
return name;
}
}
对于我们的多租户 Asp.Net Core 2.2 Web 应用程序中的许多端点,我们需要根据每个请求(使用异步数据库调用)从数据库中获取一些租户信息,然后重用该信息根据需要从请求管道中的各个点获取信息,而不必每次都调用数据库。也就是说,我们想要基于每个请求的惰性异步 属性。
根据我阅读的各种文档,scoped 服务的实例一次只能由一个线程(处理请求的线程)访问。所以,我相信这意味着我应该能够安全地执行以下操作:
// Registered as scoped in the DI container
public class TenantInfoRetriever : ITenantInfoRetriever
{
private TenantInfo _tenantInfo;
private bool _initialized;
public async Task<TenantInfo> GetAsync()
{
// This code is obviously not thread safe, which is why it's important that no two threads are running here at the same time.
if (!_initialized)
{
_tenantInfo = await GetTenantInfoAsync(); // this might actually legitimately return null, hence the need for the separate property "_initialized"
_initialized = true;
}
return _tenantInfo;
}
private async Task<TenantInfo> GetTenantInfoAsync()
{
return await DoDatabaseCallToGetTenantInfo(); // of course this would use an injected EfContext instance (scoped service)
}
}
据我所知,这应该可行,因为作用域服务不需要是线程安全的。
我的问题:我的假设是否正确,或者我是否遗漏了一些重要的东西?
Based on the various docs I've read, instances of scoped services are only ever accessed by one thread at a time (the thread processing the request).
这不完全正确。当您等待一个操作时,线程返回到线程池,当异步操作恢复时,线程池中的一个空闲线程将被拾取。 它不需要启动它的完全相同的线程。
但是,是的,不会有两个线程同时访问它,当它的作用域是从ASP.NET核心端。
当然,你自己的代码也要有所防范。 如果您同时启动两个任务/线程并 运行 它们,那么它仍然有可能同时被访问。
如果您担心它会被访问,请仔细检查锁定模式(使用可变字段)或锁定互斥锁(不是您修改的值的对象)或使用 AsyncLazy<T>
,即来自this blog post.
示例(来自 https://help.semmle.com/wiki/display/CSHARP/Double-checked+lock+is+not+thread-safe)
示例 1:
string name;
public string Name
{
get
{
lock (mutex) // GOOD: Thread-safe
{
if (name == null)
name = LoadNameFromDatabase();
return name;
}
}
}
示例 2:
volatile string name; // GOOD: Thread-safe
public string Name
{
get
{
if (name == null)
{
lock (mutex)
{
if (name == null)
name = LoadNameFromDatabase();
}
}
return name;
}
}