Как я могу заблокировать один запрос за раз, а когда он завершится, обработать самые последние, завершая более старые запросы без обработки?

Я показываю метки на карте. Когда пользователь прокручивает или уменьшает масштаб, теперь у меня есть дополнительные контакты для отображения. Если при перемещении будет добавлено еще 50 000 контактов, это может занять 4–10 секунд, поскольку запрос к базе данных сложен и, следовательно, может занять время.

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

В настоящее время я использую SemaphoreSlim, чтобы гарантировать, что одновременно обрабатывается только один ход. При разумном уровне активности со стороны пользователя это может сопровождаться 5 задачами, ожидающими своей очереди, чтобы получить больше булавок.

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

Что я хотел бы сделать, когда задача завершается и есть 2+ ожидания:

  1. Обработайте новейшую задачу.
  2. Выполните все задачи перед этой задачей.
  3. Повторите, если при этом возникли дополнительные задачи.

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

Как я могу это сделать?

(Я пробовал сохранять запросы, их параметры и отметку DateTime, а затем обрабатывать. И при освобождении мьютекса проверять каждую задачу, которая будет выполняться, чтобы увидеть, может ли она просто вернуть успех. Но у меня есть сложный беспорядок, который, я думаю, глючный.)

Рассматривали ли вы возможность использования Queue? Learn.microsoft.com/en-us/dotnet/api/…

Robert J. 16.04.2024 20:27

@РобертДж. К сожалению, это не то, что я хочу, так как я хочу, чтобы последняя запись была в стеке, а все остальные записи были удалены. И чтобы другие задачи оставались в ожидании, а затем возвращались, когда последняя из завершает обработку.

David Thielen 16.04.2024 21:21

@DavidThielen - Итак отредактируйте свой вопрос и укажите этот факт.

Security Hound 17.04.2024 02:25

@SecurityHound Кажется, я сказал это словами «Выполните все задачи перед этой задачей».

David Thielen 17.04.2024 06:09

Этот вопрос обсуждается на Meta

Bill Tür stands with Ukraine 17.04.2024 07:37

Разве не должна работать структура данных стека (потокобезопасная или заблокированная вручную)? Потому что его природа — «последним пришел/первым ушел», а это именно то, что вам нужно. Запросы попадают в стек. Когда приходит время работать над следующим запросом, вы всегда извлекаете самый новый из стека, а затем очищаете стек (таким образом отбрасывая устаревшие запросы) за одно атомарное (т. е. синхронизированное) действие.

Peter - Reinstate Monica 17.04.2024 12:25

@Peter-ReinstateMonica да и да. Я придумал более простое решение (см. ниже), но ваше предложение тоже является хорошим подходом.

David Thielen 17.04.2024 17:04
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
363
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Одна из идей — отказаться от SemaphoreSlim и использовать вместо этого цикл асинхронной обработки, который берет элементы из очереди и обрабатывает их по одному. В качестве очереди вы можете использовать ограниченный Канал<T> с емкостью 1, настроенный на отбрасывание самого старого элемента, когда нет места для самого нового элемента:

Channel<PinRequest> pinRequests = Channel.CreateBounded<PinRequest>(
    new BoundedChannelOptions(capacity: 1)
{
    FullMode = BoundedChannelFullMode.DropOldest,
});

async Task ProcessPinsAsync()
{
    await foreach (PinRequest pinRequest in channel.Reader.ReadAllAsync())
    {
        await ProcessPinRequestAsync(pinRequest);
    }
}

Чтобы добавить новый PinRequest в очередь:

pinRequests.Writer.TryWrite(new PinRequest());

Пояснение: в приведенном выше примере PinRequest не представляет собой активный Task. Он представляет состояние, необходимое для запуска Task. Task запускается методом ProcessPinRequestAsync. Итак, PinRequest — это то, от чего можно смело отказаться. Вам не придется беспокоиться о его «завершении».

Спасибо, это умный способ получить новейшую информацию. Однако как мне выполнить задачи отклоненных запросов? ТИА

David Thielen 17.04.2024 00:08

@DavidThielen вообще Task не наполнишь. Вы можете Wait/await его завершить или отменить, только если он связан с CancellationTokenSource, к которому у вашего кода есть доступ. Ваши задачи случайно не контролируются с помощью связанных TaskCompletionSource?

Theodor Zoulias 17.04.2024 00:29

Нет. Они вызывают этот метод, который возвращает задачу. Существующий подход к блокировке мьютекса работает, потому что, когда каждый из них получает свою очередь, они запускают запрос, заполняют контакты и возвращают выполненную задачу. И мне нужна эта задача для выполнения запроса. Только самое последнее задание, но оно мне нужно. Это определенно не простая проблема :) спасибо

David Thielen 17.04.2024 00:35
Ответ принят как подходящий

Я нашел простой подход. Это работает, потому что, когда поиск заканчивается, я хочу, чтобы выполнялся только самый последний запрос. Все остальные ожидающие запросы могут быть отменены (и их задачи выполнены).

Я заявляю:

/// <summary>
/// The map does not like multiple queries at once. So we use a mutex to ensure only one
/// runs at a time. THe few times this hits, there's only 2 so no point in coalescing all
/// the calls.
/// </summary>
private readonly SemaphoreSlim _mutex = new SemaphoreSlim(1, 1);

/// <summary>
/// This is an increasing counter used to discard out of date map search queries.
/// </summary>
private long _mapSearchCounter;

Затем в начале моего метода у меня есть:

var myCounter = Interlocked.Increment(ref _mapSearchCounter);

await _mutex.WaitAsync();

// if there's another LATER task to search, then we can skip this one.
var latestCounter = Interlocked.Read(ref _mapSearchCounter);
if (myCounter < latestCounter)
    return true;

И в конце метода, наконец, у меня есть:

_mutex.Release();

Заблокированные задачи могут быть освобождены в любом порядке: все более старые задачи возвращаются немедленно, а самые последние выполняют запрос и обновляют контакты.

Это должно работать хорошо. Чтобы сделать это немного менее запутанным, вы можете инкапсулировать SemaphoreSlim и _mapSearchCounter в специальный класс, например, с именем AsyncMutex. bool isLatest = await _mutex.WaitAsync(); вернет значение bool, которое будет true, если нет ожидающей новой WaitAsync операции.

Theodor Zoulias 17.04.2024 01:19

Другие вопросы по теме