#c# #dependency-injection #singleton #semaphore #asp.net-core-2.2
#c# #внедрение зависимостей #singleton #семафор #asp.net-core-2.2
Вопрос:
Я переделываю ASP.NET ПРИЛОЖЕНИЕ CORE 2.2, позволяющее избежать использования шаблона поиска служб в сочетании со статическими классами. Двойная ошибка!
Переделка включает в себя создание и внедрение Singleton object в качестве хранилища для некоторых глобальных данных. Идея здесь в том, чтобы избежать обращений к моему SQL Server за некоторыми базовыми / глобальными данными, которые используются снова и снова в запросах. Однако эти данные необходимо обновлять ежечасно (не только при запуске приложения). Итак, чтобы справиться с ситуацией, я использую 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. Для меня это действительно неопределенная территория. Кажется ли это подходящим подходом для обработки моего внедрения глобального синглтона данных во всем моем приложении? Или я должен ожидать безумной блокировки потока?
Ответ №1:
Проблема с использованием SemaphoreSlim
заключается в масштабируемости.
Поскольку это происходит в веб-приложении, справедливо предположить, что вы хотите, чтобы несколько читателей могли одновременно обращаться к данным. Однако вы (по понятным причинам) ограничиваете количество запросов к семафору, которые могут быть сгенерированы одновременно, до 1 (чтобы предотвратить одновременные запросы на чтение и запись). Это означает, что вы также будете сериализовывать все операции чтения.
Вам нужно использовать что-то вроде ReaderWriterLockSlim, чтобы разрешить чтение нескольким потокам, но обеспечить эксклюзивный доступ для записи.
Комментарии:
1. Да, вы озвучиваете то, что подсказывает мне моя интуиция. Обратная связь очень ценится! Проверит вашу ссылку на ReaderWriterLockSlim
Ответ №2:
Ответ Крейка попал мне в самую точку: использование 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;
};
}
}