为什么AsyncLock内的锁不会阻塞线程?

时间:2019-07-19 00:31:23

标签: c# asynchronous async-await locking task-parallel-library

我正试图了解AsyncLock的工作原理。

首先,下面的代码片段证明它确实有效:

var l = new AsyncLock();
var tasks = new List<Task>();
while (true)
{
    Console.ReadLine();
    var i = tasks.Count + 1;
    tasks.Add(Task.Run(async () =>
    {
        Console.WriteLine($"[{i}] Acquiring lock ...");
        using (await l.LockAsync())
        {
            Console.WriteLine($"[{i}] Lock acquired");
            await Task.Delay(-1);
        }
    }));
}

“有效”是指您可以运行任意数量的任务(按Enter键),并且线程数不会增加。如果将其替换为传统的lock,则会看到新线程已启动,这是我们试图避免的事情。

但是您在源代码中看到的第一件事是... lock

有人可以请我解释一下它是如何工作的,为什么它没有被阻止,我在这里想念的是什么?

3 个答案:

答案 0 :(得分:1)

lock inside AsyncLock正在迅速发布。每个尝试获取AsyncLock,成功获取其内部lock的任务,并且实际的锁定逻辑是通过队列完成的。

答案 1 :(得分:1)

通过将LockAsync()包装在using块中,由于LockAsync返回了将放置在对象末尾的一次性对象Key,因此该块结束时将释放锁定。 using块,并在释放锁后将其释放。参见https://github.com/StephenCleary/AsyncEx/blob/master/src/Nito.AsyncEx.Coordination/AsyncLock.cs#L182-L185

答案 2 :(得分:1)

  

有人可以请我解释一下它是如何工作的,为什么它没有被阻止,我在这里想念的是什么?

简而言之,lock只是用于保证线程安全的内部机制。 lock从未以任何方式公开,并且任何线程都无法在任何实际时间内持有该锁。这样,它类似于各种并发集合在内部使用的锁。

还有一种使用无锁编程的方法,但是我发现无锁编程非常难于编写,读取和维护。一个很好的例子(可惜不是在线的)是Dobb博士在90年代后期发表的许多文章,每一篇都试图通过更好的无锁队列实现来超越最后一篇。事实证明,它们都是错误的-在某些情况下,发现这些错误需要十多年的时间。

对于我自己的代码,我不使用无锁编程,除非代码的正确性显而易见。


就异步锁与锁的概念而言,我将尝试说明一下。我有一种感觉,我只有在使用异步协调原语时才有这种感觉。关于撰写博客文章,我已经做了很多思考,但是我没有正确的用语来使其易于理解。就是说,这里...

异步协调原语存在于与普通协调原语完全不同的平面上。同步原语阻塞线程和信号线程。异步原语仅适用于普通对象;阻止或发信号只是“按照惯例”。

因此,对于普通的lock,调用代码必须立即获得锁定。但是对于异步“锁定”,尝试的锁定只是一个请求,只是一个对象。调用代码甚至不需要await。可以请求几个锁,并将它们awaitTask.WhenAll一起使用。甚至将它们与其他事物结合在一起;代码可以做一些疯狂的事情,例如(a)等待两个锁都免费释放以等待发送信号(例如AsyncManualResetEvent),然后在信号到来时取消锁定请求首先。

从线程角度看,这有点像用户模式线程调度。合作多任务处理也有一些相似之处(相对于抢占式)。但是总的来说,异步原语被“提升”到另一个平面,在该平面中,仅可处理对象和代码块,而不处理线程。