While playing with Azure Event Hubs, I decided that I wanted to implement a thread locking mechanism that didn’t queue. That is, I want to try and get a lock on the resource, and if it’s currently in use, just forget it and move on. The default behaviour in C# is to wait for the resource. For example, consider my method:
static async Task MyProcedure()
{
Console.WriteLine($"Test1 {DateTime.Now}");
await Task.Delay(5000);
Console.WriteLine($"Test2 {DateTime.Now}");
}
I could execute this 5 times like so:
static async Task Main(string[] args)
{
Parallel.For(1, 5, (a) =>
{
MyProcedure();
});
Console.ReadLine();
}
If I wanted to lock this (just bear with me and assume that makes sense for a minute), I might do this:
private static object \_lock = new object();
static async Task Main(string[] args)
{
Parallel.For(1, 5, (a) =>
{
//MyProcedure();
Lock();
});
Console.ReadLine();
}
static void Lock()
{
Task.Run(() =>
{
lock (\_lock)
{
MyProcedure().GetAwaiter().GetResult();
}
});
}
I re-jigged the code a bit, because you can’t await inside a lock statement, and obviously, just making the method call synchronous would not be locking the asynchronous call.
So now, I’ve successfully made my asynchronous method synchronous. Each execution of `MyProcedure` will happen sequentially, and that’s because `lock` queues the locking calls behind one another.
However, imagine the Event Hub scenario that’s referenced in the post above. I have, for example, a game, and it’s sending a large volume of telemetry up to the cloud. In my particular case, I’m sending a player’s current position. If I have a locking mechanism whereby the locks are queued then I could potentially get behind; and if that happens then, at best, the data sent to the cloud will be outdated and, at worse, it will use up game resources, potentially causing a lag.
After a bit of research, I found an alterntive:
private static object \_lock = new object();
static async Task Main(string[] args)
{
Parallel.For(1, 5, (a) =>
{
//MyProcedure();
//Lock();
TestTryEnter();
});
Console.ReadLine();
}
static async Task TestTryEnter()
{
bool lockTaken = false;
try
{
Monitor.TryEnter(\_lock, 0, ref lockTaken);
if (lockTaken)
{
await MyProcedure();
}
else
{
Console.WriteLine("Could not get lock");
}
}
finally
{
if (lockTaken)
{
Monitor.Exit(\_lock);
}
}
}
So here, I try to get the lock, and if the resource is already locked, I simply give up and go home. There are obviously a very limited number of uses for this; however, my Event Hub scenario, described above, is one of them. Depending on the type of data that you’re transmitting, it may make much more sense to have a go, and if you’re in the middle of another call, simply abandon the current one.