Я наткнулся на действительно странную проблему с ReaderWriterLock при написании модульного теста. Я попробовал тестировать метод UpgradeToWriterLock с параметром таймаута, установленным в 50 миллисекунд.
В основном потоке я беру блокировку читателя, а затем запускаю множество задач. В задачах я также беру считыватель, а затем пытаюсь выполнить обновление до автора с тайм-аутом. Это должно терпеть неудачу на каждом из них, так как основной поток удерживает блокировку чтения. Поскольку время составляет 50 миллисекунд, задачи должны генерировать исключение тайм-аута и завершать. Если я начну более 10 задач, это не так. Они застревают в UpgradeToWriterLock.
Может ли кто-нибудь объяснить это? Весь исходный код ниже.
[TestMethod]
public void UpgradeLockFailTest()
{
// strangely when more than 10 threads then it gets stuck on UpgradeToWriterLock regardless of the timeout
const int THREADS_COUNT = 20;
// 50 milliseconds
const int TIMEOUT = 50;
// create the main reader writer lock
ReaderWriterLock rwl = new ReaderWriterLock();
// acquire the reader lock on the main thread
rwl.AcquireReaderLock(TIMEOUT);
// create and start all the tasks
Task[] tasks = new Task[THREADS_COUNT];
for (int i = 0; i < THREADS_COUNT; i++)
{
tasks[i] = Task.Factory.StartNew(() =>
{
try
{
// acquire the reader lock on the worker thread
rwl.AcquireReaderLock(TIMEOUT);
// acquire the writer lock on the worker thread
rwl.UpgradeToWriterLock(TIMEOUT); // <-- GETS STUCK HERE AND DOESN'T RESPECT TIMEOUT
}
finally
{
rwl.ReleaseLock();
}
});
}
// should be enough for all the tasks to be created
Thread.Sleep(2000);
try
{
// wait for all tasks
Task.WaitAll(tasks); // <-- GETS STUCK HERE BECAUSE THE TASKS ARE STUCK ON UpgradeToWriterLock
}
catch (AggregateException ae)
{
Assert.AreEqual(THREADS_COUNT, ae.InnerExceptions.Count);
}
// release all the locks on the main thread
rwl.ReleaseLock();
}
Интересно, если я отключаю блокировку чтения основного потока, прежде чем ждать задач, все работает так, как ожидалось. Исправлено правильное количество исключений таймаута.
Вы уверены, что все застряли, а не только последнее?
Из документации UpgradeToWriterLock
:
Исключение тайм-аута не выбрасывается до тех пор, пока поток, называемый методом UpgradeToWriterLock, не сможет восстановить блокировку считывателя. Если нет других потоков, ожидающих блокировки записи, это происходит немедленно. Тем не менее, если другой поток помещен в очередь для блокировки записи, поток, который вызвал метод UpgradeToWriterLock, не может восстановить блокировку считывателя, пока все текущие считыватели не освободят свои блокировки, а один поток приобрел и освободил блокировку записи. Это верно, даже если другой поток, который запросил блокировку записи, запросил его после текущего потока, называемого методом UpgradeToWriterLock.
Обратите внимание на множество условий, которые должны возникнуть для исключения исключения таймаута. "если другой поток поставлен в очередь для блокировки записи, поток, который вызвал метод UpgradeToWriterLock, не может восстановить блокировку считывателя [и выбросить исключение] до":
Вы никогда не позволяете этим условиям возникать в последнем потоке, который пытается обновиться, поэтому вы всегда будете ждать UpgradeToWriterLock
, и поэтому WaitAll
тоже WaitAll
ждать. Если ваш основной поток также попытался обновиться до ожидания, я думаю, что все будет в порядке.