Regarding usage of AsyncLock vs the overhead.

Apr 16, 2014 at 1:34 PM
Edited Apr 16, 2014 at 4:01 PM
Hi,

First off, great library for these asynchronous primitives.

I'm wondering if AsyncLock, and the overhead asynchrony adds, is actually worth using it instead of a synchronous lock, as done with the Monitor class.

This is of course, a very subjective question, but my point is, in a scenario where many threads are contending for the lock, does the asynchronous nature actually have any benefit for a really short task (say, a locking over a simple bool change) or is it better to stick to a simple lock.

From my understanding, the AsyncLock should basically prevent forcing the CLR to make a OS level context switch when it enters a wait state, instead utilize the lighter weight transitions of the thread pool. Does that have a significant advantage for smaller tasks such as a simple bool change?
Coordinator
Apr 16, 2014 at 5:19 PM
No, that's not what AsyncLock is for.

AsyncLock (and all the other asynchronous coordination primitives) are designed to coordinate asynchronous code.

If the code under lock is all synchronous (in other words, it has no await statements), then you should use a regular lock {} statement, Monitor, etc.
AsyncLock and friends are designed for situations where the code needs to await while holding a lock. Regular (synchronous) locks do not support this scenario.

AsyncLock is less performant than the lock statement; you should use it only if you need to await while holding the lock.

-Steve
Apr 16, 2014 at 8:21 PM
Edited Apr 16, 2014 at 8:22 PM
I think you misunderstood that I wasn't aware of the library's main usage. I guess I didn't quite phrase it clearly, and that led to the confusion. Sorry about that.

My question was, due to the CLR threadpool usage vs the OS context changes on each wait, is it possible that in a scenario with a large number of threads (which in an instance would often run into a wait, would async locks actually perform better, since in the AsyncLock, all that's done is locking the _mutex object, which should be acquired easily, and then ending up queued.

But anyway, ran in a few experiments, and it doesn't really seem to be the case. And the ability to not have control over the number of threads the CLR uses also doesn't help the experiment.

Thanks for the prompt reply though :)

-PVL
Apr 16, 2014 at 8:52 PM
Edited Apr 16, 2014 at 9:03 PM
To make it even clear, let me give a different scenario - Quite opposite actually. When the a long running task tries to acquire the lock. What happens is, the system basically chokes on the number of hardware threads. Below is an example iterating it. So, that got me thinking, if it could actually replace conventional locking, when the number of contentions are going to be high.

My system is a core i5 with 4 hardware threads, so it chokes on a maximum of 4 threads at a time. And it takes a long time to start waiting for the lock in the rest, while the async version has no issues waiting all 10 at the same time, and the program will work as a common programmer would expect it to.

This is of course, not directly related to locks, but rather to the consequence of waiting on threads due to locking.

Sync locking:
internal class Program
{
    private static object syncRoot = new object();

    private static void Main(string[] args)
    {
        Console.WriteLine("Hello");
        for (int i = 0; i < 10; i++)
        {
            Task.Run(
                () =>
                    {
                        Console.WriteLine("Waiting for lock..");
                        lock (syncRoot)
                        {
                            Console.WriteLine("Cool!. Done");
                            Task.Delay(2000).Wait();
                            Console.WriteLine("Released.");
                        }
                    });
        }

        Console.WriteLine("All done folks!");

        Console.ReadLine();
    }
}

Async variant:
internal class Program
{
    private static AsyncLock syncRoot = new AsyncLock();

    private static void Main(string[] args)
    {
        Console.WriteLine("Hello");
        for (int i = 0; i < 10; i++)
        {
            Task.Run(
                async () =>
                    {
                        Console.WriteLine("Waiting for lock..");
                        using (await syncRoot.LockAsync())
                        {
                            Console.WriteLine("Cool!. Done");
                            Task.Delay(2000).Wait();
                            Console.WriteLine("Released.");
                        }
                    });
        }

        Console.WriteLine("All done folks!");
        Console.ReadLine();
    }
}
Apr 17, 2014 at 5:51 AM
My 2 cents: In my experience, if you have to keep a lock longer than 100ms, you need to look at an asynchronous lock like AsyncLock which does not lock up threads (some documents suggest 60ms, but it also depends on how many threads will contend). Especially if the lock is over an asynchronous IO operation.
Apr 17, 2014 at 5:57 PM
Actually, if you change the above Task.Delay parameter to even a 1 millisecond, or lesser, you'd notice that the program will choke on the maximum number of threads. In fact, in my system, I can go as low as a few thousands ticks, and it'd still choke, while async locks should have absolutely no problems.

And my primary question, though, was really about a very small operation. The above was just written as a illustration better clarify the concept, as to why it might make sense. The scenario I'm talking about it is a very small operations (say, a bool change), but a very-high number of locks contending for it - That's not uncommon in tons of server applications, and database applications.
Coordinator
Apr 18, 2014 at 12:55 AM
To be honest, this is a use that I did not plan for AsyncLock.

In my own code, I actually avoid locks as much as humanly possible; I prefer immutable/concurrent collections, or use Parallel/Dataflow to handle the synchronization for me. When I do use a lock, I do my best to absolutely minimize the code under lock. So it's literally been years since I've had a highly-contended lock scenario.

But hey, if AsyncLock helps you in this case, go for it! :)

-Steve