Как избежать проблем с многопоточностью DbContext, вызванных частыми запросами HttpRequest? - PullRequest
0 голосов
/ 07 ноября 2019

Я взял кодовую базу у кого-то другого. Это веб-приложение, построенное на Angular 8 (клиент) и .NET Core 3.0 (сервер).

Краткое описание приложения:

  1. Частые уведомления хранятся в базе данных,с прикрепленным к нему SqlTableDependency для обнаружения новых уведомлений.
  2. Когда появляются новые уведомления, сервер предлагает всем клиентам запрашивать обновленный список на основе их пользовательских фильтров. Эти клиент-серверные запросы происходят через HttpPost с фильтром в качестве параметра.

Проблема возникает, когда за один раз приходит слишком много уведомлений. Например, когда поступают 10 новых уведомлений, сервер одновременно отправляет 10 запросов на обновление клиенту, в результате чего клиент немедленно отправляет 10 запросов HttpPost в API.

API получает фильтр из POST,использует его для запроса к базе данных и возвращает отфильтрованный результат вызывающему клиенту. Однако, когда 10 из них прибывают одновременно, это вызывает ошибку DbContext - более конкретно:

Вторая операция, запущенная в этом контексте перед завершением предыдущей операции. Обычно это вызвано тем, что разные потоки используют один и тот же экземпляр DbContext.

public class AlarmController : Controller
{
    private readonly IAlarmRepository alarmRepo;
    private readonly ISiteRepository siteRepo;
    public AlarmController(IAlarmRepository alarmRepo, ISiteRepository siteRepo)
    {
        this.alarmRepo = alarmRepo;
        this.siteRepo = siteRepo;
    }

    [HttpPost("filter")]
    public async Task<IActionResult> FilterAlarm([FromBody] AlarmRequest alarmRequest)
    {
        var snmpReciverList = await this.alarmRepo.GetFilteredSNMPReceiverHistory(alarmRequest.FromDate, alarmRequest.ToDate);
        var siteList = await this.siteRepo.GetSiteListFiltered(int.Parse(alarmRequest.Filter), alarmRequest.SiteName);

        return Ok(await SNMPHistoryMapping.DoMapping(siteList, snmpReciverList);
    }

Этот HttpPost возвращает Ok() со списком запрошенных данных, в котором выполняется некоторое сопоставление:

        IEnumerable<Site> sites = siteList;
        IEnumerable<SnmpreceiverHistory> histories = snmpReceiverList;

        IEnumerable<SNMPHistoryResponse> data = (from s in sites
                    join rh in histories on s.Address equals rh.Ipaddress
                    where priority > 0 ? s.SitePriority == priority : true
                    && !string.IsNullOrEmpty(trap) ? rh.AlarmDescription.Contains(trap) : true
                    select new SNMPHistoryResponse()
                    {
                        AlarmDescription = rh.AlarmDescription,
                        EventType = rh.EventType,
                        OnOffStatus = rh.OnOffStatus,
                        ParentSiteName = TraceFullParentDescription(s.Parent),
                        ReceiveTime = rh.ReceiveTime,
                        RepeatCount = rh.RepeatCount,
                        SiteName = s.Description,
                        SitePriority = s.SitePriority,
                        Status = AlarmStatus.GetStatusDescription(rh.EventType),
                        Value = rh.Value
                    });

Когда несколько этих [HttpPost("filter")] запросов поступают одновременно, создается впечатление, что для каждого из них создается новый поток. Все они подключаются к одному и тому же DbContext, и следующий запрос начинается до того, как завершится предыдущий.

Я могу решить эту проблему, помещая задержки между каждым запросом от клиента, но я хочу более надежное решение на стороне сервера, чтобыэто, эффективно обрабатывая эти конкретные запросы последовательно.

Обратите внимание, что это EF Core и .NET Core 3.0, которые не имеют SynchronizationContext.

...