C# ReaderWriterLockSlim поврежден после прерывания потока, который вызывает ExitReadLock
Я столкнулся со сценарием, когда ReaderWriterLockSlim, кажется, сломался после ряда юридических действий.
Поток это:
- Поток 1 берет писателя lock1
- Поток 2 пытается взять блокировку читателя lock1 -
- Поток 2 прерывается и вызывает lock1.ExitReadLock Поток 2 не получил блокировку. Кажется, что исключение должно быть брошено.
- Поток 1 выходит из блокировки писателя lock1
- Любой поток, который пытается взять lock1.EnterReadLock, будет блокироваться навсегда
После описанного выше этапа 3 отладчик показывает, что lock1.CurrentReadCount поврежден - похоже, переполнен до 0x7FFFFFF.
Интересно, кто-нибудь сталкивался с этим, или, может быть, я что-то упустил.
Код, который воспроизводит это:
[TestMethod]
public void ReproTest()
{
var rwlock = new ReaderWriterLockSlim();
rwlock.EnterWriteLock();
bool taken = false;
var reader = new Thread(() =>
{
try
{
rwlock.EnterReadLock();
s_logger.Info("Enter");
}
catch (ThreadInterruptedException)
{
rwlock.ExitReadLock();
}
});
reader.Name = "Reader";
reader.Start();
Thread.Sleep(1000);
reader.Interrupt();
Thread.Sleep(1000);
rwlock.ExitWriteLock();
while (!taken)
{
taken = rwlock.TryEnterReadLock(1000);
}
Thread.Sleep(1000);
}
3 ответа
Это похоже на ошибку в фреймворке (протестировано на v3.5 и v4.0). ExitReadLock()
должен бросить SynchronizationLockException
, но не в этом случае. В самом деле, вы можете вызвать очень похожую проблему гораздо проще с помощью следующего:
rwlock.EnterReadLock();
rwlock.ExitReadLock();
// This should throw a SynchronizationLockException but doesn't
rwlock.ExitReadLock();
// At this point, rwlock.CurrentReaderCount = 0x0fffffff
(По факту, ExitReadLock()
повредит блокировку, если она вызывается без соответствия EnterReadLock()
на любой теме, которая ранее вошла в замок.)
Проблема возникает только тогда, когда ReaderWriterLockSlim
создается с помощью конструктора без параметров, или с LockRecursionPolicy.NoRecursion
, Если создан с LockRecursionPolicy.SupportsRecursion
, он не будет поврежден непревзойденным ExitReadLock()
,
Если вы ожидаете, что поток чтения будет прерван во время ожидания входа в блокировку, я бы предложил изменить метод потока чтения на:
var reader = new Thread(() =>
{
var entered = false;
try
{
rwlock.EnterReadLock();
entered = true;
s_logger.Info("Enter");
}
finally
{
if (entered) rwlock.ExitReadLock();
}
});
Читатель никогда не входит в замок чтения. Он сидит в ожидании выпуска записи. Когда он прерывается, вы пытаетесь выйти, даже если вы никогда не входили, в результате чего счетчик чтения опустился ниже 0, я думаю:)
Код, который исправляет то, на что указали @Lasse и @Jeremy:
static public void ReproTest()
{
var rwlock = new ReaderWriterLockSlim();
rwlock.EnterWriteLock();
s_logger.Info("0:Enter");
bool taken1 = false;
var reader = new Thread(() =>
{
try
{
rwlock.EnterReadLock();
s_logger.Info("1:Enter");
// only set to true if taken
taken1 = true;
}
catch (ThreadInterruptedException)
{
// only release if taken
if (taken1)
rwlock.ExitReadLock();
taken1 = false;
}
});
reader.Name = "Reader";
reader.Start();
Thread.Sleep(1000);
reader.Interrupt();
Thread.Sleep(1000);
rwlock.ExitWriteLock();
// 2nd taken variable here only so we can see state of taken1
bool taken2 = taken1;
while (!taken2)
{
taken2 = rwlock.TryEnterReadLock(1000);
s_logger.Info("2:Enter");
}
Thread.Sleep(1000);
}
При запуске выходные данные отладки правильно показывают, что блокировка записи взята, 1-я блокировка чтения НЕ взята, и 2-я блокировка чтения взята
0:Enter
A first chance exception of type 'System.Threading.ThreadInterruptedException' occurred in mscorlib.dll
The thread 'Reader' (0x1358) has exited with code 0 (0x0).
2:Enter