#c# #asp.net #threadpool
#c# #asp.net #threadpool
Вопрос:
Я работаю над библиотекой классов, которая регистрирует детали аудита веб-приложения в нескольких типах источников данных (файл, xml, база данных) на основе политик, определенных в файле веб-конфигурации.
Мой метод журнала аудита имеет подпись, похожую на эту: public static void LogInfo(User пользователь, модуль модуля, список lst);
Веб-приложение использует этот метод для регистрации важных деталей, таких как предупреждения, сведения об ошибках и даже исключения.
Поскольку в одном рабочем процессе существует более 700 вызовов этих методов, я подумал о том, чтобы сделать их асинхронными. Я использовал простой метод из класса ThreadPool под названием QueueUserWorkItem
ThreadPool.QueueUserWorkItem(o => LogInfo(User user, Module module, List<Object> lst) );
но это не гарантирует порядок, в котором рабочий элемент был помещен в очередь к нему. Хотя вся моя информация была зарегистрирована, но весь порядок был перепутан. В моем текстовом файле мои журналы были расположены не в том порядке, в котором они были вызваны.
Есть ли способ, которым я могу контролировать порядок вызываемых потоков, используя QueueUserWorkItem?
Ответ №1:
Я не думаю, что вы можете указать порядок при использовании QueueUserWorkItem
.
Чтобы запустить ведение журнала параллельно (в некотором фоновом потоке), вы могли бы использовать ConcurrentQueue<T>
. Это потокобезопасная коллекция, доступ к которой возможен из нескольких потоков. Вы могли бы создать один рабочий элемент (или поток), который считывает элементы из коллекции и записывает их в файл. Ваше основное приложение добавило бы элементы в коллекцию. Тот факт, что вы добавляете элементы в коллекцию из одного потока, должен гарантировать, что они будут прочитаны в правильном порядке.
Чтобы упростить задачу, вы можете сохранять Action
значения в очереди:
ConcurrentQueue<Action> logOperations = new ConcurrentQueue<Action>();
// To add logging operation from main thread:
logOperations.Add(() => LogInfo(user, module, lst));
Фоновая задача может просто брать Action
s из очереди и запускать их:
// Start this when you create the `logOperations` collection
ThreadPool.QueueUserWorkItem(o => {
Action op;
// Repeatedly take log operations amp; run them
while (logOperations.TryDequeue(out op)) op();
});
Если вам нужно остановить фоновый процессор (который записывает данные в журнал), вы можете создать CancellationTokenSource
, а также завершить while
цикл, когда токен отменяется (основным потоком). Это можно проверить с помощью IsCancellationRequested
свойства (см. MSDN)
Комментарии:
1. РЕДАКТИРОВАТЬ Изначально я предлагал использовать
BlockingCollection
, но в данном случае достаточно использоватьConcurrentQueue
. (На самом деле, я даже понял это до того, как увидел ответ Бризио :-))2. Я буду вызывать метод аудита несколько раз из событий жизненного цикла страницы. На самом деле это также может быть вызвано несколько раз из одного метода. Может ли ConcurrentQueue<T> позаботиться об этом?
3. @Kunal: Да, вы можете добавить несколько действий (путем
Add
повторного вызова).4. Вам следует пересмотреть
BlockingCollection
.ConcurrentQueue.TryDequeue
возвращаетсяfalse
при первом опустошении очереди, что приведет к завершению вашего потока. Вы можете написать логику для ожидания элементов и т.д., Или вы можете просто использоватьBlockingCollection
.ConcurrentQueue.TryDequeue
не поддерживает отмену. Для этого вам понадобитсяBlockingCollection
.5. @Jim: Да, я думаю, вы правы — использование
BlockingCollection
, вероятно, было лучшей идеей! (вам не нужна блокировка на стороне производителя, но отмена — довольно полезная вещь).
Ответ №2:
Одним из способов решения этой проблемы было бы поместить ваши данные в очередь, а затем выполнить выбор одной задачи из этой очереди и записать их по порядку. Если вы используете .net 4.0, вы могли бы использовать ConcurrentQueue, который является потокобезопасным, в противном случае также работала бы простая очередь с надлежащими блокировками.
Поток, использующий очередь, мог бы затем периодически проверять наличие любого элемента внутри очереди, и для каждого из них он мог бы регистрировать. Таким образом, длительная операция (ведение журнала) может выполняться в отдельном потоке, тогда как в основном потоке вы просто добавляете.