Очередь FIFO приводит к тому, что процессор IIS достигает максимума в 100%
У меня есть одноэлементный класс службы в IIS как часть веб-приложения (служба должна быть одноэлементной по причинам кэширования данных). Клиенты браузера, которые делают запросы к сервису, могут получить один из трех результатов:
1) в кэше есть данные, и данные не просрочены (устаревшие) - мы возвращаем эти данные. Очень быстрый. 2) Срок действия кэшированных данных истек, но другой запрос уже запрашивает базу данных. Мы вернули кэшированные данные. 3) срок хранения кэшированных данных истек, и запросы не выполняются. выполнение запросов к БД. Этот запрос перемещается вперед, чтобы сделать запрос.
Однако запросы базы данных, нацеленные на хранимые процедуры с тем же именем, должны быть помещены в очередь (требование).
Таким образом, я написал этот класс queue, который предназначен для того, чтобы помещать эти запросы в очередь и запускать их последовательно, а не одновременно. Эти классы очередей создаются по мере необходимости и хранятся в списке в классе singleton. Когда запрос перемещается в Часть (3), он находит класс очереди, соответствующий его сохраненному имя процедуры и отправляет запрос в класс queue. Затем он ожидает, пока данные не будут возвращены из БД, чтобы он мог обслуживать HTML-запрос.
К сожалению, после нескольких часов работы с этим кодом серверный процесс работает на 100%.
Я не уверен, что лучший способ-это улучшить его, потому что многопоточное кодирование-не моя специальность.
Код класса очереди выглядит следующим образом:
public ReportTable GetReportTable(ReportQuery query)
{
lock (_queue)
{
_queue.Enqueue(query);
Monitor.Pulse(_queue);
}
lock (_queue)
{
var firstQueryInQueue = _queue.Peek();
while (_inUse || firstQueryInQueue == null || firstQueryInQueue.GetHashCode() != query.GetHashCode())
{
Monitor.Pulse(_queue);
Monitor.Wait(_queue);
}
_inUse = true;
firstQueryInQueue = _queue.Dequeue();
var table = firstQueryInQueue.GetNewReportTable();
_inUse = false;
Monitor.Pulse(_queue);
return table;
}
}
2 ответа:
Я не знаю, понимаю ли я проблему. но вы можете переписать его очень просто
private object _lockObj=new object(); public ReportTable GetReportTable(ReportQuery query) { lock(_lockObj){ var table = query.GetNewReportTable(); return table; } }
Так вот что я сделал, чтобы исправить это.
public ReportTable GetReportTable(ReportQuery query) { lock (_queue) { _queue.Enqueue(query); Monitor.Pulse(_queue); } lock (_queue) { var firstQueryInQueue = _queue.Peek(); while (_inUse || firstQueryInQueue == null || firstQueryInQueue.GetHashCode() != query.GetHashCode()) { Monitor.Wait(_queue); } _inUse = true; firstQueryInQueue = _queue.Dequeue(); var table = firstQueryInQueue.GetNewReportTable(); _inUse = false; Monitor.Pulse(_queue); return table; } }
Причина, по которой он не работал раньше, заключалась в том, что у меня не было полного понимания монитора.Wait () и монитор.Пульс(). Я использовал Pulse () в неправильном месте кода. К счастью, есть очень хороший ответ здесь, который довольно хорошо описывает Wait() и Pulse ().
Ключ должен пульсировать () после того, как коллекция изменена, и дать последующим потокам в очереди шанс проверить условие, которое является: is my запрос первый в очереди? И кто-то еще уже делает запрос? Не пройдя этот тест, поток вызывает функцию Wait (), которая помещает его в очередь ожидания и не связывает процессорные циклы. Когда поток, который находится впереди и выполняет запрос, завершается, он переворачивает флаг _inUse на false и вызывает Pulse (), пробуждая один из следующих потоков, чтобы он мог проверить условие.
После внедрения этого решения и просмотра Диспетчера задач в течение дня, я был рад видеть 1% к 5% нагрузки на сервер в течение нескольких часов, и процессор никогда не поднимался до 100%, как раньше.
Я много читал об этом, и кажется, что PulseAll() может быть лучшим вызовом в этом сценарии, но пока Pulse() работает, и мы не столкнулись ни с какими проблемами.