我正试图了解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
有人可以请我解释一下它是如何工作的,为什么它没有被阻止,我在这里想念的是什么?
答案 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
。可以请求几个锁,并将它们await
与Task.WhenAll
一起使用。甚至将它们与其他事物结合在一起;代码可以做一些疯狂的事情,例如(a)等待两个锁都免费释放或以等待发送信号(例如AsyncManualResetEvent
),然后在信号到来时取消锁定请求首先。
从线程角度看,这有点像用户模式线程调度。合作多任务处理也有一些相似之处(相对于抢占式)。但是总的来说,异步原语被“提升”到另一个平面,在该平面中,仅可处理对象和代码块,而不处理线程。