При написании многопоточного кода вы должны использовать свой мозг даже больше, чем обычно. Вы должны логически рассуждать о каждой отдельной строке кода, независимо от того, является ли она поточно-безопасной или нет. Это все равно, что доказать правильность математической формулы - вы не можете доказать такие вещи, как «N + 1> N для всех N», просто приведя примеры значений N, с которыми формула верна. Точно так же доказательство того, что класс является потокобезопасным, невозможно путем написания тестовых примеров, которые пытаются выявить проблемы с ним. С помощью теста можно только доказать, что есть неисправность, но не то, что неисправностей нет.
Лучшее, что вы можете сделать, - это минимизировать потребность в многопоточном коде. Предпочтительно, чтобы приложение не имело многопоточного кода (например, полагаясь на поточно-ориентированные библиотеки и подходящие шаблоны проектирования), или оно должно быть ограничено очень маленькой областью. Ваш StackQueue
класс выглядит достаточно простым, так что вы можете сделать его безопасным для многопотоковой работы, немного подумав.
Предполагая, что реализации Stack
и Queue
являются поточно-ориентированными (я не знаю библиотеки .NET), вам просто нужно сделать Next()
поточно-ориентированным. Count
уже является поточно-ориентированным, поскольку ни один клиент не может безопасно использовать возвращаемое им значение без использования клиентской блокировки - зависимости состояния между методами в противном случае нарушали бы код.
Next()
не является поточно-ориентированным, поскольку имеет зависимости состояния между методами. Если потоки T1 и T2 вызывают stack.Count
одновременно, и он возвращает 1, то один из них получит значение с stack.Pop()
, а другой вызовет stack.Pop()
, когда стек пуст (который, как представляется, выбрасывает InvalidOperationException
). Вам понадобится стек и очередь с неблокирующими версиями Pop()
и Dequeue()
(те, которые возвращают ноль, когда пусты). Тогда код будет потокобезопасным при написании так:
private WebRequestInfo Next()
{
WebRequestInfo next = stack.PopOrNull()
if (next == null)
{
next = queue.DequeueOrNull();
}
return next;
}