Asp.net Core DI: использование SemaphoreSlim для операций записи И чтения с помощью Singleton

#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;
        };
    }
}