简体   繁体   English

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

[英]Why the lock inside AsyncLock does not block the thread?

I'm trying to understand how the AsyncLock works. 我试图了解AsyncLock的工作原理。

First of all, here's a snippet to prove that it actually works: 首先,下面的代码片段证明它确实有效:

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);
        }
    }));
}

By "works" I mean that you can run as many tasks as you want (by hitting Enter) and the number of threads doesn't grow. “工作”是指您可以运行任意数量的任务(按Enter键),并且线程数不会增加。 If you replace it with traditional lock , you'll see that the new threads are started, which is what we try to avoid. 如果将其替换为传统的lock ,则会看到新线程已启动,这是我们试图避免的。

But the first thing you see in the source code is... the lock 但是您在源代码中看到的第一件事是...

Can somebody please explain me how this works, why it doesn't block, and what am I missing here? 有人可以请我解释一下它是如何工作的,为什么它不能阻止,我在这里想念的是什么?

The lock inside AsyncLock is beeing released very quickly. AsyncLock内部AsyncLock就会释放。 Each task which tries to acquire AsyncLock , successfully acquires it's internal lock and the actual locking logic is done with a queue. 每个尝试获取AsyncLock任务都成功获取其内部lock ,并且实际的锁定逻辑是通过队列完成的。

By wrapping LockAsync() within using block, the lock is being released when the block ends since LockAsync returns a disposable object Key which will be disposed at the end of the using block, and upon disposing the lock will be released. 通过将LockAsync()包装在using块内,由于LockAsync返回了将放置在using块末尾的一次性对象Key ,并且在释放锁之后,该块结束LockAsync释放该锁。 see https://github.com/StephenCleary/AsyncEx/blob/master/src/Nito.AsyncEx.Coordination/AsyncLock.cs#L182-L185 参见https://github.com/StephenCleary/AsyncEx/blob/master/src/Nito.AsyncEx.Coordination/AsyncLock.cs#L182-L185

Can somebody please explain me how this works, why it doesn't block, and what am I missing here? 有人可以请我解释一下它是如何工作的,为什么它不能阻止,我在这里想念的是什么?

The short answer is that lock is just an internal mechanism used to guarantee thread safety. 简短的答案是, lock只是用于确保线程安全的内部机制。 The lock is never exposed in any way, and there's no way for any thread to hold that lock for any real amount of time. 永远不会以任何方式公开该lock ,并且任何线程都无法在任何实际时间内持有该锁。 In this way, it's similar to the locks used internally by various concurrent collections. 这样,它类似于各种并发集合在内部使用的锁。

There is an alternate approach that uses lock-free programming, but I have found lock-free programming to be extremely difficult to write, read, and maintain. 还有一种使用无锁编程的方法,但是我发现无锁编程非常难于编写,读取和维护。 A great example of this (which is sadly not online) was a bunch of Dr. Dobb's articles in the late '90s, each one trying to out-do the last with a better lock-free queue implementation. 一个很好的例子(可惜不是在线的)是Dobb博士在90年代后期发表的许多文章,每一篇都试图通过更好的无锁队列实现来超越最后一篇。 It turns out they were all faulty - in some cases, the bugs took more than a decade to find. 事实证明它们都是错误的-在某些情况下,发现这些错误需要十多年的时间。

For my own code, I do not use lock-free programming, except where the correctness of the code is trivially obvious. 对于我自己的代码,我不使用无锁编程,除非代码的正确性显而易见。


As far as the async lock vs lock concepts, I'm going to take a stab at explaining this. 至于异步锁与锁的概念,我将用一点刺痛的方式来解释。 There's a feeling I get that I have only felt when working with asynchronous coordination primitives. 我有一种感觉,我只有在使用异步协调原语时才有这种感觉。 It's something I've thought a lot about writing a blog post on, but I don't have the right words to make it understandable. 关于撰写博客文章,我已经做了很多思考,但是我没有正确的用语来使其易于理解。 That said, here goes... 也就是说,这里...

Asynchronous coordination primitives exist on a completely different plane than normal coordination primitives. 异步协调原语存在于与普通协调原语完全不同的平面上。 Synchronous primitives block threads and signal threads. 同步原语阻塞线程和信号线程。 Asynchronous primitives just work on plain objects; 异步原语仅适用于普通对象; the blocking or signaling is just "by convention". 阻塞或信令仅仅是“按照惯例”。

So, with a normal lock , the calling code must take the lock immediately. 因此,使用普通lock ,调用代码必须立即进行锁定。 But with an asynchronous "lock", the attempted lock is just a request, just an object. 但是对于异步“锁定”,尝试的锁定只是一个请求,只是一个对象。 The calling code doesn't even need to await it. 调用代码甚至不需要await它。 It's possible to request several locks and await them all together with Task.WhenAll . 可以请求多个锁并与Task.WhenAll一起await它们。 Or even combine them with other things; 甚至将它们与其他事物结合在一起; code can do crazy things like (a)wait for two locks to both be free or for a signal (like AsyncManualResetEvent ) to be sent, and then cancel the lock requests if the signal comes in first. 代码可以做一些疯狂的事情,例如(a)等待两个锁都释放等待发送一个信号(如AsyncManualResetEvent ),然后在信号首先出现时取消锁定请求。

From a thread perspective, it's kinda-sorta like user-mode thread scheduling. 从线程的角度来看,这有点像用户模式线程调度。 There's also some similarities to cooperative multitasking (as opposed to preemptive). 合作多任务处理也有一些相似之处(相对于抢占式)。 But overall, the asynchronous primitives are "lifted" to a different plane, where one works only with objects and blocks of code, not threads. 但是总的来说,异步原语被“提升”到另一平面,在该平面中,仅可处理对象和代码块,而不处理线程。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM