public void CountTest() { RemoteExecutor.Invoke(() => { const int TimersPerThread = 64; int processorCount = Environment.ProcessorCount; int totalTimerCount = processorCount * TimersPerThread; var timers = new List <Timer>(totalTimerCount); TimerCallback timerCallback = _ => { }; var startCreateTimerThreads = new ManualResetEvent(false); Action createTimerThreadStart = () => { startCreateTimerThreads.WaitOne(); for (int i = 0; i < TimersPerThread; ++i) { lock (timers) { timers.Add( new Timer( timerCallback, null, ThreadTestHelpers.UnexpectedTimeoutMilliseconds, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); Assert.True(Timer.ActiveCount >= timers.Count); } } }; var waitsForThread = new Action[processorCount]; for (int i = 0; i < processorCount; ++i) { Thread t = ThreadTestHelpers.CreateGuardedThread(out waitsForThread[i], createTimerThreadStart); t.IsBackground = true; t.Start(); } startCreateTimerThreads.Set(); foreach (Action waitForThread in waitsForThread) { waitForThread(); } // To leave some room for unknown timers to be scheduled and removed, remove a large number of timers at a time and // verify that the timer count has decreased while (timers.Count > 0) { long timerCountBeforeRemove = Timer.ActiveCount; int endIndex = timers.Count - processorCount * 8; for (int i = timers.Count - 1; i >= Math.Max(0, endIndex); --i) { timers[i].Dispose(); timers.RemoveAt(i); } if (endIndex >= 0) { Assert.True(Timer.ActiveCount < timerCountBeforeRemove); } } }).Dispose(); }
public void AbandonExisting( string name, WaitHandleWaitType waitType, int waitCount, int notAbandonedWaitIndex, bool isNotAbandonedWaitObjectSignaled, bool abandonDuringWait) { ThreadTestHelpers.RunTestInBackgroundThread(() => { using (var m = new Mutex(false, name)) using (Mutex m2 = waitCount == 1 ? null : new Mutex(false, name == null ? null : name + "_2")) using (ManualResetEvent e = waitCount == 1 ? null : new ManualResetEvent(isNotAbandonedWaitObjectSignaled)) using (ManualResetEvent threadReadyForAbandon = abandonDuringWait ? new ManualResetEvent(false) : null) using (ManualResetEvent abandonSoon = abandonDuringWait ? new ManualResetEvent(false) : null) { WaitHandle[] waitHandles = null; if (waitType != WaitHandleWaitType.WaitOne) { waitHandles = new WaitHandle[waitCount]; if (waitCount == 1) { waitHandles[0] = m; } else { waitHandles[notAbandonedWaitIndex] = e; waitHandles[notAbandonedWaitIndex == 0 ? 1 : 0] = m; waitHandles[notAbandonedWaitIndex == 2 ? 1 : 2] = m2; } } Thread t = ThreadTestHelpers.CreateGuardedThread(out Action waitForThread, () => { Assert.True(m.WaitOne(0)); if (m2 != null) { Assert.True(m2.WaitOne(0)); } if (abandonDuringWait) { threadReadyForAbandon.Set(); abandonSoon.CheckedWait(); Thread.Sleep(ThreadTestHelpers.ExpectedTimeoutMilliseconds); } // don't release the mutexes; abandon them on this thread }); t.IsBackground = true; t.Start(); if (abandonDuringWait) { threadReadyForAbandon.CheckedWait(); abandonSoon.Set(); } else { waitForThread(); } AbandonedMutexException ame; switch (waitType) { case WaitHandleWaitType.WaitOne: ame = AssertExtensions.Throws <AbandonedMutexException, bool>( () => m.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); Assert.Equal(-1, ame.MutexIndex); Assert.Null(ame.Mutex); break; case WaitHandleWaitType.WaitAny: if (waitCount != 1 && isNotAbandonedWaitObjectSignaled && notAbandonedWaitIndex == 0) { Assert.Equal(0, WaitHandle.WaitAny(waitHandles, 0)); AssertExtensions.Throws <AbandonedMutexException, bool>( () => m.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); AssertExtensions.Throws <AbandonedMutexException, bool>( () => m2.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); break; } if (waitCount != 1 && isNotAbandonedWaitObjectSignaled && notAbandonedWaitIndex != 0) { ame = Assert.Throws <AbandonedMutexException>(() => { ThreadTestHelpers.WaitForCondition(() => { // Actually expecting an exception from WaitAny(), but there may be a delay before // the mutex is actually released and abandoned. If there is no exception, the // WaitAny() must have succeeded due to the event being signaled. int r = WaitHandle.WaitAny(waitHandles, ThreadTestHelpers.UnexpectedTimeoutMilliseconds); Assert.Equal(notAbandonedWaitIndex, r); return(false); }); }); } else { ame = AssertExtensions.Throws <AbandonedMutexException, int>( () => WaitHandle.WaitAny(waitHandles, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); } // Due to a potential delay in abandoning mutexes, either mutex may have been seen to be // abandoned first Assert.True(ame.Mutex == m || (m2 != null && ame.Mutex == m2)); int mIndex = waitCount != 1 && notAbandonedWaitIndex == 0 ? 1 : 0; int m2Index = waitCount != 1 && notAbandonedWaitIndex == 2 ? 1 : 2; if (ame.Mutex == m) { Assert.Equal(mIndex, ame.MutexIndex); } else { Assert.True(!isNotAbandonedWaitObjectSignaled || m2Index < notAbandonedWaitIndex); Assert.Equal(m2Index, ame.MutexIndex); } // Verify that the other mutex also gets abandoned if (ame.MutexIndex == mIndex) { if (m2 != null) { AssertExtensions.Throws <AbandonedMutexException, bool>( () => m2.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); } } else { AssertExtensions.Throws <AbandonedMutexException, bool>( () => m.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); } break; case WaitHandleWaitType.WaitAll: if (waitCount != 1 && !isNotAbandonedWaitObjectSignaled) { Assert.False(WaitHandle.WaitAll(waitHandles, ThreadTestHelpers.ExpectedTimeoutMilliseconds * 2)); Assert.True(e.Set()); } ame = AssertExtensions.Throws <AbandonedMutexException, bool>( () => WaitHandle.WaitAll(waitHandles, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); Assert.Equal(-1, ame.MutexIndex); Assert.Null(ame.Mutex); break; } if (abandonDuringWait) { waitForThread(); } m.ReleaseMutex(); m2?.ReleaseMutex(); } }); }
public void Ctor_TryCreateGlobalMutexTest_Uwp() { ThreadTestHelpers.RunTestInBackgroundThread(() => Assert.Throws <UnauthorizedAccessException>(() => new Mutex(false, "Global\\" + Guid.NewGuid().ToString("N")))); }
public static void WaitNotificationTest() { ThreadTestHelpers.RunTestInBackgroundThread(() => { var tsc = new TestSynchronizationContext(); SynchronizationContext.SetSynchronizationContext(tsc); Assert.Same(tsc, SynchronizationContext.Current); var e = new ManualResetEvent(false); tsc.WaitAction = () => e.Set(); Assert.False(tsc.IsWaitNotificationRequired()); Assert.False(e.WaitOne(0)); tsc.SetWaitNotificationRequired(); Assert.True(tsc.IsWaitNotificationRequired()); Assert.True(e.WaitOne(0)); var mres = new ManualResetEventSlim(); tsc.WaitAction = () => mres.Set(); mres.Reset(); mres.CheckedWait(); e.Reset(); tsc.WaitAction = () => e.Set(); SynchronizationContext.SetSynchronizationContext(new TestSynchronizationContext()); Assert.False(e.WaitOne(0)); SynchronizationContext.SetSynchronizationContext(tsc); Assert.True(e.WaitOne(0)); e.Reset(); e.CheckedWait(); e.Reset(); var lockObj = new object(); var lockAcquiredFromBackground = new AutoResetEvent(false); Action waitForThread; Thread t = ThreadTestHelpers.CreateGuardedThread(out waitForThread, () => { lock (lockObj) { lockAcquiredFromBackground.Set(); e.CheckedWait(); } }); t.IsBackground = true; t.Start(); lockAcquiredFromBackground.CheckedWait(); Assert.True(Monitor.TryEnter(lockObj, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); Monitor.Exit(lockObj); waitForThread(); e.Reset(); var m = new Mutex(); t = ThreadTestHelpers.CreateGuardedThread(out waitForThread, () => { m.CheckedWait(); try { lockAcquiredFromBackground.Set(); e.CheckedWait(); } finally { m.ReleaseMutex(); } }); t.IsBackground = true; t.Start(); lockAcquiredFromBackground.CheckedWait(); m.CheckedWait(); m.ReleaseMutex(); waitForThread(); }); }
public static void FlowTest() { ThreadTestHelpers.RunTestInBackgroundThread(() => { var asyncLocal = new AsyncLocal <int>(); asyncLocal.Value = 1; var asyncFlowControl = default(AsyncFlowControl); Action <Action, Action> verifySuppressRestore = (suppressFlow, restoreFlow) => { VerifyExecutionContextFlow(asyncLocal, 1); ExecutionContext executionContext2 = ExecutionContext.Capture(); suppressFlow(); VerifyExecutionContextFlow(asyncLocal, 0); VerifyExecutionContext(executionContext2, asyncLocal, 1); executionContext2 = ExecutionContext.Capture(); restoreFlow(); VerifyExecutionContextFlow(asyncLocal, 1); VerifyExecutionContext(executionContext2, asyncLocal, 0); }; verifySuppressRestore( () => asyncFlowControl = ExecutionContext.SuppressFlow(), () => asyncFlowControl.Undo()); verifySuppressRestore( () => asyncFlowControl = ExecutionContext.SuppressFlow(), () => asyncFlowControl.Dispose()); verifySuppressRestore( () => ExecutionContext.SuppressFlow(), () => ExecutionContext.RestoreFlow()); Assert.Throws <InvalidOperationException>(() => ExecutionContext.RestoreFlow()); asyncFlowControl = ExecutionContext.SuppressFlow(); Assert.Throws <InvalidOperationException>(() => ExecutionContext.SuppressFlow()); ThreadTestHelpers.RunTestInBackgroundThread(() => { ExecutionContext.SuppressFlow(); Assert.Throws <InvalidOperationException>(() => asyncFlowControl.Undo()); Assert.Throws <InvalidOperationException>(() => asyncFlowControl.Dispose()); ExecutionContext.RestoreFlow(); }); asyncFlowControl.Undo(); Assert.Throws <InvalidOperationException>(() => asyncFlowControl.Undo()); Assert.Throws <InvalidOperationException>(() => asyncFlowControl.Dispose()); // Changing an async local value does not prevent undoing a flow-suppressed execution context. In .NET Core, the // execution context is immutable, so changing an async local value changes the execution context instance, // contrary to the .NET Framework. asyncFlowControl = ExecutionContext.SuppressFlow(); asyncLocal.Value = 2; asyncFlowControl.Undo(); VerifyExecutionContextFlow(asyncLocal, 2); asyncFlowControl = ExecutionContext.SuppressFlow(); asyncLocal.Value = 3; asyncFlowControl.Dispose(); VerifyExecutionContextFlow(asyncLocal, 3); ExecutionContext.SuppressFlow(); asyncLocal.Value = 4; ExecutionContext.RestoreFlow(); VerifyExecutionContextFlow(asyncLocal, 4); // An async flow control cannot be undone when a different execution context is applied. The .NET Framework // mutates the execution context when its state changes, and only changes the instance when an execution context // is applied (for instance, through ExecutionContext.Run). The framework prevents a suppressed-flow execution // context from being applied by returning null from ExecutionContext.Capture, so the only type of execution // context that can be applied is one whose flow is not suppressed. After suppressing flow and changing an async // local's value, the .NET Framework verifies that a different execution context has not been applied by // checking the execution context instance against the one saved from when flow was suppressed. In .NET Core, // since the execution context instance will change after changing the async local's value, it verifies that a // different execution context has not been applied, by instead ensuring that the current execution context's // flow is suppressed. { ExecutionContext executionContext = null; Action verifyCannotUndoAsyncFlowControlAfterChangingExecutionContext = () => { ExecutionContext.Run( executionContext, state => { Assert.Throws <InvalidOperationException>(() => asyncFlowControl.Undo()); Assert.Throws <InvalidOperationException>(() => asyncFlowControl.Dispose()); }, null); }; executionContext = ExecutionContext.Capture(); asyncFlowControl = ExecutionContext.SuppressFlow(); verifyCannotUndoAsyncFlowControlAfterChangingExecutionContext(); asyncFlowControl.Undo(); executionContext = ExecutionContext.Capture(); asyncFlowControl = ExecutionContext.SuppressFlow(); asyncLocal.Value = 5; verifyCannotUndoAsyncFlowControlAfterChangingExecutionContext(); asyncFlowControl.Undo(); VerifyExecutionContextFlow(asyncLocal, 5); } }); }
public static void ValuesGetterDoesNotThrowUnexpectedExceptionWhenDisposed() { var startTest = new ManualResetEvent(false); var gotUnexpectedException = new ManualResetEvent(false); ThreadLocal <int> threadLocal = null; bool stop = false; Action waitForCreatorDisposer; Thread creatorDisposer = ThreadTestHelpers.CreateGuardedThread(out waitForCreatorDisposer, () => { startTest.CheckedWait(); do { var tl = new ThreadLocal <int>(trackAllValues: true); Volatile.Write(ref threadLocal, tl); tl.Value = 1; tl.Dispose(); } while (!Volatile.Read(ref stop)); }); creatorDisposer.IsBackground = true; creatorDisposer.Start(); int readerCount = Math.Max(1, Environment.ProcessorCount - 1); var waitsForReader = new Action[readerCount]; for (int i = 0; i < readerCount; ++i) { Thread reader = ThreadTestHelpers.CreateGuardedThread(out waitsForReader[i], () => { startTest.CheckedWait(); do { var tl = Volatile.Read(ref threadLocal); if (tl == null) { continue; } try { IList <int> values = tl.Values; } catch (ObjectDisposedException) { } catch { gotUnexpectedException.Set(); throw; } } while (!Volatile.Read(ref stop)); }); reader.IsBackground = true; reader.Start(); } startTest.Set(); bool failed = gotUnexpectedException.WaitOne(500); Volatile.Write(ref stop, true); foreach (Action waitForReader in waitsForReader) { waitForReader(); } waitForCreatorDisposer(); Assert.False(failed); }
public static void Enter_HasToWait() { var thinLock = new object(); var awareLock = new object(); // Actually transition the aware lock to an aware lock by having a background thread wait for a lock { Action waitForThread; Thread t = ThreadTestHelpers.CreateGuardedThread(out waitForThread, () => Assert.False(Monitor.TryEnter(awareLock, ThreadTestHelpers.ExpectedTimeoutMilliseconds))); t.IsBackground = true; lock (awareLock) { t.Start(); waitForThread(); } } // When the current thread has the lock, have background threads wait for the lock in various ways. After a short // duration, release the lock and allow the background threads to acquire the lock. { var backgroundTestDelegates = new List <Action <object> >(); Barrier readyBarrier = null; backgroundTestDelegates.Add(lockObj => { readyBarrier.SignalAndWait(); Monitor.Enter(lockObj); Monitor.Exit(lockObj); }); backgroundTestDelegates.Add(lockObj => { readyBarrier.SignalAndWait(); bool lockTaken = false; Monitor.Enter(lockObj, ref lockTaken); Assert.True(lockTaken); Monitor.Exit(lockObj); }); backgroundTestDelegates.Add(lockObj => { readyBarrier.SignalAndWait(); lock (lockObj) { } }); backgroundTestDelegates.Add(lockObj => { readyBarrier.SignalAndWait(); Assert.True(Monitor.TryEnter(lockObj, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); Monitor.Exit(lockObj); }); backgroundTestDelegates.Add(lockObj => { readyBarrier.SignalAndWait(); Assert.True( Monitor.TryEnter(lockObj, TimeSpan.FromMilliseconds(ThreadTestHelpers.UnexpectedTimeoutMilliseconds))); Monitor.Exit(lockObj); }); backgroundTestDelegates.Add(lockObj => { readyBarrier.SignalAndWait(); bool lockTaken = false; Monitor.TryEnter(lockObj, ThreadTestHelpers.UnexpectedTimeoutMilliseconds, ref lockTaken); Assert.True(lockTaken); Monitor.Exit(lockObj); }); backgroundTestDelegates.Add(lockObj => { readyBarrier.SignalAndWait(); bool lockTaken = false; Monitor.TryEnter( lockObj, TimeSpan.FromMilliseconds(ThreadTestHelpers.UnexpectedTimeoutMilliseconds), ref lockTaken); Assert.True(lockTaken); Monitor.Exit(lockObj); }); int testCount = backgroundTestDelegates.Count * 2; // two iterations each, one for thin lock and one for aware lock readyBarrier = new Barrier(testCount + 1); // plus main thread var waitForThreadArray = new Action[testCount]; for (int i = 0; i < backgroundTestDelegates.Count; ++i) { int icopy = i; // for use in delegates Thread t = ThreadTestHelpers.CreateGuardedThread(out waitForThreadArray[i * 2], () => backgroundTestDelegates[icopy](thinLock)); t.IsBackground = true; t.Start(); t = ThreadTestHelpers.CreateGuardedThread(out waitForThreadArray[i * 2 + 1], () => backgroundTestDelegates[icopy](awareLock)); t.IsBackground = true; t.Start(); } lock (thinLock) { lock (awareLock) { readyBarrier.SignalAndWait(ThreadTestHelpers.UnexpectedTimeoutMilliseconds); Thread.Sleep(ThreadTestHelpers.ExpectedTimeoutMilliseconds); } } foreach (Action waitForThread in waitForThreadArray) { waitForThread(); } } // When the current thread has the lock, have background threads wait for the lock in various ways and time out // after a short duration { var backgroundTestDelegates = new List <Action <object> >(); Barrier readyBarrier = null; backgroundTestDelegates.Add(lockObj => { readyBarrier.SignalAndWait(); Assert.False(Monitor.TryEnter(lockObj, ThreadTestHelpers.ExpectedTimeoutMilliseconds)); }); backgroundTestDelegates.Add(lockObj => { readyBarrier.SignalAndWait(); Assert.False( Monitor.TryEnter(lockObj, TimeSpan.FromMilliseconds(ThreadTestHelpers.ExpectedTimeoutMilliseconds))); }); backgroundTestDelegates.Add(lockObj => { readyBarrier.SignalAndWait(); bool lockTaken = false; Monitor.TryEnter(lockObj, ThreadTestHelpers.ExpectedTimeoutMilliseconds, ref lockTaken); Assert.False(lockTaken); }); backgroundTestDelegates.Add(lockObj => { readyBarrier.SignalAndWait(); bool lockTaken = false; Monitor.TryEnter( lockObj, TimeSpan.FromMilliseconds(ThreadTestHelpers.ExpectedTimeoutMilliseconds), ref lockTaken); Assert.False(lockTaken); }); int testCount = backgroundTestDelegates.Count * 2; // two iterations each, one for thin lock and one for aware lock readyBarrier = new Barrier(testCount + 1); // plus main thread var waitForThreadArray = new Action[testCount]; for (int i = 0; i < backgroundTestDelegates.Count; ++i) { int icopy = i; // for use in delegates Thread t = ThreadTestHelpers.CreateGuardedThread(out waitForThreadArray[i * 2], () => backgroundTestDelegates[icopy](thinLock)); t.IsBackground = true; t.Start(); t = ThreadTestHelpers.CreateGuardedThread(out waitForThreadArray[i * 2 + 1], () => backgroundTestDelegates[icopy](awareLock)); t.IsBackground = true; t.Start(); } lock (thinLock) { lock (awareLock) { readyBarrier.SignalAndWait(ThreadTestHelpers.UnexpectedTimeoutMilliseconds); foreach (Action waitForThread in waitForThreadArray) { waitForThread(); } } } } }
public static void BasicLockTest() { var trwl = new TestReaderWriterLock(); TestLockCookie tlc; var threadReady = new AutoResetEvent(false); var continueThread = new AutoResetEvent(false); Action checkForThreadErrors, waitForThread; Thread t = ThreadTestHelpers.CreateGuardedThread(out checkForThreadErrors, out waitForThread, () => { TestLockCookie tlc2; Action switchToMainThread = () => { threadReady.Set(); continueThread.CheckedWait(); }; switchToMainThread(); // Multiple readers from multiple threads { trwl.AcquireReaderLock(); trwl.AcquireReaderLock(); switchToMainThread(); trwl.ReleaseReaderLock(); switchToMainThread(); trwl.ReleaseReaderLock(); switchToMainThread(); trwl.AcquireReaderLock(); trwl.ReleaseReaderLock(); switchToMainThread(); } // What can be done when a read lock is held { trwl.AcquireReaderLock(); switchToMainThread(); // Any thread can take a read lock trwl.AcquireReaderLock(); trwl.ReleaseReaderLock(); switchToMainThread(); // No thread can take a write lock trwl.AcquireWriterLock(TimeoutExceptionHResult); trwl.AcquireReaderLock(); trwl.UpgradeToWriterLock(TimeoutExceptionHResult); trwl.ReleaseReaderLock(); switchToMainThread(); trwl.ReleaseReaderLock(); switchToMainThread(); // Owning thread releases read lock when upgrading trwl.AcquireWriterLock(); trwl.ReleaseWriterLock(); switchToMainThread(); // Owning thread cannot upgrade if there are other readers or writers trwl.AcquireReaderLock(); switchToMainThread(); trwl.ReleaseReaderLock(); trwl.AcquireWriterLock(); switchToMainThread(); trwl.ReleaseWriterLock(); switchToMainThread(); } // What can be done when a write lock is held { // Write lock acquired through AcquireWriteLock is exclusive trwl.AcquireWriterLock(); switchToMainThread(); trwl.ReleaseWriterLock(); switchToMainThread(); // Write lock acquired through upgrading is also exclusive trwl.AcquireReaderLock(); tlc2 = trwl.UpgradeToWriterLock(); switchToMainThread(); trwl.DowngradeFromWriterLock(tlc2); trwl.ReleaseReaderLock(); switchToMainThread(); // Write lock acquired through restore is also exclusive trwl.AcquireWriterLock(); tlc = trwl.ReleaseLock(); trwl.RestoreLock(tlc); switchToMainThread(); trwl.ReleaseWriterLock(); switchToMainThread(); } }); t.IsBackground = true; t.Start(); Action beginSwitchToBackgroundThread = () => continueThread.Set(); Action endSwitchToBackgroundThread = () => { try { threadReady.CheckedWait(); } finally { checkForThreadErrors(); } }; Action switchToBackgroundThread = () => { beginSwitchToBackgroundThread(); endSwitchToBackgroundThread(); }; endSwitchToBackgroundThread(); // Multiple readers from muliple threads { trwl.AcquireReaderLock(); trwl.AcquireReaderLock(); switchToBackgroundThread(); // AcquireReaderLock * 2 trwl.ReleaseReaderLock(); switchToBackgroundThread(); // ReleaseReaderLock // Release/restore the read lock while a read lock is held by another thread tlc = trwl.ReleaseLock(); trwl.RestoreLock(tlc); switchToBackgroundThread(); // ReleaseReaderLock // Downgrade to read lock allows another thread to acquire read lock tlc = trwl.UpgradeToWriterLock(); trwl.DowngradeFromWriterLock(tlc); switchToBackgroundThread(); // AcquireReaderLock, ReleaseReaderLock trwl.ReleaseReaderLock(); } // What can be done when a read lock is held { switchToBackgroundThread(); // AcquireReaderLock { // Any thread can take a read lock trwl.AcquireReaderLock(); trwl.ReleaseReaderLock(); switchToBackgroundThread(); // same as above // No thread can take a write lock trwl.AcquireWriterLock(TimeoutExceptionHResult); trwl.AcquireReaderLock(); trwl.UpgradeToWriterLock(TimeoutExceptionHResult); switchToBackgroundThread(); // same as above trwl.ReleaseReaderLock(); // Other threads cannot upgrade to a write lock, but the owning thread can trwl.AcquireReaderLock(); trwl.UpgradeToWriterLock(TimeoutExceptionHResult); trwl.ReleaseReaderLock(); } switchToBackgroundThread(); // ReleaseReaderLock // Owning thread releases read lock when upgrading trwl.AcquireReaderLock(); beginSwitchToBackgroundThread(); // AcquireWriterLock: background thread gets blocked trwl.UpgradeToWriterLock(); // unblocks background thread: ReleaseWriterLock trwl.ReleaseWriterLock(); endSwitchToBackgroundThread(); // Owning thread cannot upgrade if there are other readers or writers trwl.AcquireReaderLock(); switchToBackgroundThread(); // AcquireReaderLock trwl.UpgradeToWriterLock(TimeoutExceptionHResult); trwl.ReleaseReaderLock(); switchToBackgroundThread(); // ReleaseReaderLock, AcquireWriterLock trwl.UpgradeToWriterLock(TimeoutExceptionHResult); switchToBackgroundThread(); // ReleaseWriterLock } // What can be done when a write lock is held { trwl.AcquireWriterLock(); TestLockCookie restoreToWriteLockTlc = trwl.ReleaseLock(); Action verifyCannotAcquireLock = () => { trwl.AcquireReaderLock(TimeoutExceptionHResult); trwl.AcquireWriterLock(TimeoutExceptionHResult); trwl.UpgradeToWriterLock(TimeoutExceptionHResult); }; Action verifyCanAcquireLock = () => { trwl.AcquireReaderLock(); tlc = trwl.UpgradeToWriterLock(); trwl.DowngradeFromWriterLock(tlc); trwl.ReleaseReaderLock(); trwl.AcquireWriterLock(); trwl.ReleaseWriterLock(); trwl.RestoreLock(restoreToWriteLockTlc.Clone()); trwl.ReleaseWriterLock(); }; // Write lock acquired through AcquireWriteLock is exclusive switchToBackgroundThread(); // AcquireWriterLock verifyCannotAcquireLock(); switchToBackgroundThread(); // ReleaseWriterLock verifyCanAcquireLock(); // Write lock acquired through upgrading is also exclusive switchToBackgroundThread(); // AcquireReaderLock, UpgradeToWriterLock verifyCannotAcquireLock(); switchToBackgroundThread(); // DowngradeFromWriterLock, ReleaseReaderLock verifyCanAcquireLock(); // Write lock acquired through restore is also exclusive switchToBackgroundThread(); // AcquireWriterLock, ReleaseLock, RestoreLock verifyCannotAcquireLock(); switchToBackgroundThread(); // ReleaseWriterLock verifyCanAcquireLock(); } beginSwitchToBackgroundThread(); waitForThread(); trwl.Dispose(); }