Asp.net 核心 DI:使用 SemaphoreSlim 对 Singleton 进行读写操作
Asp.net Core DI: Using SemaphoreSlim for write AND read operations with Singleton
我正在重新构建 ASP.NET CORE 2.2 应用程序以避免将服务定位器模式与静态 类 结合使用。双坏!
重组涉及创建和注入单例对象作为某些全局数据的存储库。这里的想法是避免在请求中反复使用的某些 basic/global 数据访问我的 SQL 服务器。但是,此数据需要每小时更新一次(不仅仅是在应用程序启动时)。因此,为了管理这种情况,我使用 SemaphoreSlim 来处理对数据对象的一次一个访问。
这是我正在做的事情的配对草图:
namespace MyApp.Global
{
public interface IMyGlobalDataService
{
Task<List<ImportantDataItem>> GetFilteredDataOfMyList(string prop1);
Task LoadMyImportantDataListAsync();
}
public class MyGlobalDataService: IMyGlobalDataService
{
private MyDbContext _myDbContext;
private readonly SemaphoreSlim myImportantDataLock = new SemaphoreSlim(1, 1);
private List<ImportantDataItem> myImportantDataList { get; set; }
public async Task<List<ImportantDataItem>> GetFilteredDataOfMyList(string prop1)
{
List<ImportantDataItem> list;
myImportantDataLock.WaitAsync();
try
{
list = myImportantDataList.Where(itm => itm.Prop1 == prop1).ToList();
}
finally
{
myImportantDataLock.Release();
}
return list;
}
public async Task LoadMyImportantDataListAsync()
{
// this method gets called when the Service is created and once every hour thereafter
myImportantDataLock.WaitAsync();
try
{
this.MyImportantDataList = await _myDbContext.ImportantDataItems.ToListAsync();
}
finally
{
myImportantDataLock.Release();
}
return;
}
public MyGlobalDataService(MyDbContext myDbContext) {
_myDbContext = myDbContext;
};
}
}
所以实际上我使用 SemaphoreSlim 来限制一次一个线程的访问,用于对 myImportantDataList 的读取和更新。这对我来说真的是一个不确定的领域。这似乎是处理我在整个应用程序中注入全局数据单例的合适方法吗?或者我应该期待疯狂的线程 locking/blocking?
使用 SemaphoreSlim
的问题是可扩展性。
由于这是在 Web 应用程序中,因此可以公平地假设您希望多个 reader 能够同时访问数据。但是,您(可以理解)将可以同时生成的信号量请求数限制为 1(以防止并发读取和写入请求)。这意味着您也将序列化所有读取。
您需要使用 ReaderWriterLockSlim 之类的东西来允许多个线程进行读取,但确保对写入进行独占访问。
Creyke 的回答一针见血:使用 ReaderWriterLockSlim。所以我将其标记为已接受的答案。但我正在发布我修改后的解决方案,以防它对任何人都有帮助。需要注意的是,我正在使用以下包为 ReaderWriterLockSlim 提供异步功能:https://www.nuget.org/packages/Nito.AsyncEx/
using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.Text;
namespace MyApp.Global
{
public interface IMyGlobalDataService
{
Task<List<ImportantDataItem>> GetFilteredDataOfMyList(string prop1);
Task LoadMyImportantDataListAsync();
}
public class MyGlobalDataService : IMyGlobalDataService
{
private MyDbContext _myDbContext;
private readonly AsyncReaderWriterLock myImportantDataLock = new AsyncReaderWriterLock();
private List<ImportantDataItem> myImportantDataList { get; set; }
public async Task<List<ImportantDataItem>> GetFilteredDataOfMyList(string prop1)
{
List<ImportantDataItem> list;
using (await myImportantDataLock.ReaderLockAsync())
{
list = myImportantDataList.Where(itm => itm.Prop1 == prop1).ToList();
}
return list;
}
public async Task LoadMyImportantDataListAsync()
{
// this method gets called when the Service is created and once every hour thereafter
using (await myImportantDataLock.WriterLockAsync())
{
this.MyImportantDataList = await _myDbContext.ImportantDataItems.ToListAsync();
}
return;
}
public MyGlobalDataService(MyDbContext myDbContext)
{
_myDbContext = myDbContext;
};
}
}
我正在重新构建 ASP.NET CORE 2.2 应用程序以避免将服务定位器模式与静态 类 结合使用。双坏!
重组涉及创建和注入单例对象作为某些全局数据的存储库。这里的想法是避免在请求中反复使用的某些 basic/global 数据访问我的 SQL 服务器。但是,此数据需要每小时更新一次(不仅仅是在应用程序启动时)。因此,为了管理这种情况,我使用 SemaphoreSlim 来处理对数据对象的一次一个访问。
这是我正在做的事情的配对草图:
namespace MyApp.Global
{
public interface IMyGlobalDataService
{
Task<List<ImportantDataItem>> GetFilteredDataOfMyList(string prop1);
Task LoadMyImportantDataListAsync();
}
public class MyGlobalDataService: IMyGlobalDataService
{
private MyDbContext _myDbContext;
private readonly SemaphoreSlim myImportantDataLock = new SemaphoreSlim(1, 1);
private List<ImportantDataItem> myImportantDataList { get; set; }
public async Task<List<ImportantDataItem>> GetFilteredDataOfMyList(string prop1)
{
List<ImportantDataItem> list;
myImportantDataLock.WaitAsync();
try
{
list = myImportantDataList.Where(itm => itm.Prop1 == prop1).ToList();
}
finally
{
myImportantDataLock.Release();
}
return list;
}
public async Task LoadMyImportantDataListAsync()
{
// this method gets called when the Service is created and once every hour thereafter
myImportantDataLock.WaitAsync();
try
{
this.MyImportantDataList = await _myDbContext.ImportantDataItems.ToListAsync();
}
finally
{
myImportantDataLock.Release();
}
return;
}
public MyGlobalDataService(MyDbContext myDbContext) {
_myDbContext = myDbContext;
};
}
}
所以实际上我使用 SemaphoreSlim 来限制一次一个线程的访问,用于对 myImportantDataList 的读取和更新。这对我来说真的是一个不确定的领域。这似乎是处理我在整个应用程序中注入全局数据单例的合适方法吗?或者我应该期待疯狂的线程 locking/blocking?
使用 SemaphoreSlim
的问题是可扩展性。
由于这是在 Web 应用程序中,因此可以公平地假设您希望多个 reader 能够同时访问数据。但是,您(可以理解)将可以同时生成的信号量请求数限制为 1(以防止并发读取和写入请求)。这意味着您也将序列化所有读取。
您需要使用 ReaderWriterLockSlim 之类的东西来允许多个线程进行读取,但确保对写入进行独占访问。
Creyke 的回答一针见血:使用 ReaderWriterLockSlim。所以我将其标记为已接受的答案。但我正在发布我修改后的解决方案,以防它对任何人都有帮助。需要注意的是,我正在使用以下包为 ReaderWriterLockSlim 提供异步功能:https://www.nuget.org/packages/Nito.AsyncEx/
using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.Text;
namespace MyApp.Global
{
public interface IMyGlobalDataService
{
Task<List<ImportantDataItem>> GetFilteredDataOfMyList(string prop1);
Task LoadMyImportantDataListAsync();
}
public class MyGlobalDataService : IMyGlobalDataService
{
private MyDbContext _myDbContext;
private readonly AsyncReaderWriterLock myImportantDataLock = new AsyncReaderWriterLock();
private List<ImportantDataItem> myImportantDataList { get; set; }
public async Task<List<ImportantDataItem>> GetFilteredDataOfMyList(string prop1)
{
List<ImportantDataItem> list;
using (await myImportantDataLock.ReaderLockAsync())
{
list = myImportantDataList.Where(itm => itm.Prop1 == prop1).ToList();
}
return list;
}
public async Task LoadMyImportantDataListAsync()
{
// this method gets called when the Service is created and once every hour thereafter
using (await myImportantDataLock.WriterLockAsync())
{
this.MyImportantDataList = await _myDbContext.ImportantDataItems.ToListAsync();
}
return;
}
public MyGlobalDataService(MyDbContext myDbContext)
{
_myDbContext = myDbContext;
};
}
}