Skip to content

Instantly share code, notes, and snippets.

@danield137
Created December 28, 2022 00:17
Show Gist options
  • Save danield137/26911863126cbbe1991b604b610b112b to your computer and use it in GitHub Desktop.
Save danield137/26911863126cbbe1991b604b610b112b to your computer and use it in GitHub Desktop.
Example of locking a write to resource, and providing an awaitable for readers
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace ProperLockingTester
{
public class WellLockedResource
{
private readonly object m_lock;
private bool lockedState = false;
private TaskCompletionSource<bool> m_taskCompletionSource;
public WellLockedResource()
{
m_lock = new object();
}
public Task<bool> ProtectedActionAsync(bool withWait = false)
{
Monitor.Enter(m_lock);
var messageFormat = "{0} | {1} | {2}";
try
{
Console.WriteLine(messageFormat, DateTime.Now, Thread.CurrentThread.ManagedThreadId, $"Entered Lock. state = '{lockedState}'");
// no lock required, just skip to waiting
if (lockedState == true)
{
Monitor.Exit(m_lock);
if (withWait && m_taskCompletionSource != null)
{
Console.WriteLine(messageFormat, DateTime.Now, Thread.CurrentThread.ManagedThreadId, $" Wait requested");
return m_taskCompletionSource.Task;
}
else
{
return Task.FromResult(true);
}
}
if (withWait)
{
Console.WriteLine(messageFormat, DateTime.Now, Thread.CurrentThread.ManagedThreadId, $" <!> Ugrade lock <!>");
if (lockedState == false)
{
Console.WriteLine(messageFormat, DateTime.Now, Thread.CurrentThread.ManagedThreadId, $" Mutation - WAIT");
Thread.Sleep(1000);
Console.WriteLine(messageFormat, DateTime.Now, Thread.CurrentThread.ManagedThreadId, $" Mutation - EXEC ");
m_taskCompletionSource = new TaskCompletionSource<bool>();
lockedState = true;
Monitor.Exit(m_lock);
Console.WriteLine(messageFormat, DateTime.Now, Thread.CurrentThread.ManagedThreadId, $" <!> Downgrade lock <!>");
Thread.Sleep(3000);
if (!m_taskCompletionSource.Task.IsCompleted)
{
m_taskCompletionSource.SetResult(true);
}
Console.WriteLine(messageFormat, DateTime.Now, Thread.CurrentThread.ManagedThreadId, $" Mutation exit");
}
}
}
finally
{
Console.WriteLine(messageFormat, DateTime.Now, Thread.CurrentThread.ManagedThreadId, $"Exit lock");
if (Monitor.IsEntered(m_lock))
{
Monitor.Exit(m_lock);
}
}
return Task.FromResult(true);
}
}
public class SillyTest
{
public static void SillyLockTest()
{
var messageFormat = "{0} | {1} | {2}";
var resource = new WellLockedResource();
ConcurrentBag<Task<bool>> bools = new ConcurrentBag<Task<bool>>();
// Enter a slow write lock
Task.Run(() => { bools.Add(resource.ProtectedActionAsync(true)); });
Thread.Sleep(100);
// Enter a slow read lock
Task.Run(() => { bools.Add(resource.ProtectedActionAsync(true)); });
// Enter another write lock - should wait to finish
// Enter a fast read lock
Task.Run(() => { bools.Add(resource.ProtectedActionAsync(false)); });
Thread.Sleep(100);
// Enter another write lock - should wait to finish
Task.Run(() => { bools.Add(resource.ProtectedActionAsync(false)); });
// Enter a slow read lock
Task.Run(() => { bools.Add(resource.ProtectedActionAsync(true)); });
// Enter another write lock - should wait to finish
// Enter a fast read lock
Thread.Sleep(3000);
Task.Run(() => { bools.Add(resource.ProtectedActionAsync(false)); });
Console.WriteLine(messageFormat, DateTime.Now, Thread.CurrentThread.ManagedThreadId, $"--- WAITING ---");
Task.WhenAll(bools.ToArray()).ConfigureAwait(false).GetAwaiter().GetResult();
Console.ReadLine();
}
}
internal class Program
{
static void Main(string[] args)
{
SillyTest.SillyLockTest();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment