资源有自己的锁定和解锁代码来处理并发性。任何线程都可以修改资源。
那里有一面黄旗。我发现从长远来看,保护资源(而不是让资源保护自己)的设计通常更好。
单击 Button1 时,其处理程序会对 r 本身进行一些修改,然后异步调用 _IndependentResourceModifierAsync(),这会在生成的任务中对 r 进行一些修改。 _IndependentResourceModifierAsync() 在执行此操作之前获取 r 的锁。另外,因为处理程序正在干扰 r 本身,所以它也获取了 r 的锁。
还有一个危险信号。递归锁几乎总是一个坏主意。我在博客上解释了我的推理。 http://blog.stephencleary.com/2013/04/recursive-re-entrant-locks.html
我还收到了有关设计的另一个警告:
如果在主线程锁定资源时单击button1或button2,则会引发异常。 (不能使用监视器或互斥体,因为它们是线程驱动的)
这对我来说听起来不对。还有其他方法可以做到这一点吗?随着状态变化禁用按钮似乎是一个更好的方法。
我强烈建议重构以消除锁递归的要求。然后你可以使用SemaphoreSlim
with WaitAsync
异步获取锁并Wait(0)
对于“尝试锁定”。
所以你的代码最终会看起来像这样:
class Resource
{
private readonly SemaphoreSlim mutex = new SemaphoreSlim(1);
// Take the lock immediately, throwing an exception if it isn't available.
public IDisposable ImmediateLock()
{
if (!mutex.Wait(0))
throw new InvalidOperationException("Cannot acquire resource");
return new AnonymousDisposable(() => mutex.Release());
}
// Take the lock asynchronously.
public async Task<IDisposable> LockAsync()
{
await mutex.WaitAsync();
return new AnonymousDisposable(() => mutex.Release());
}
}
async void button1Click(..)
{
using (r.ImmediateLock())
{
... // mess with r
await _IndependentResourceModiferUnsafeAsync();
}
}
async void button2Click(..)
{
using (r.ImmediateLock())
{
await _IndependentResourceModiferUnsafeAsync();
}
}
async Task _IndependentResourceModiferAsync()
{
using (await r.LockAsync())
{
await _IndependentResourceModiferUnsafeAsync();
}
}
async Task _IndependentResourceModiferUnsafeAsync()
{
... // code here assumes it owns the resource lock
}
我做了很多搜索,寻找其他遇到过这个问题的人,但都一无所获。这可能意味着我把事情过于复杂化了,但我很好奇人们对此有何看法。
在很长一段时间里,这是不可能的(完全是这样)。对于 .NET 4.5,这是可能的,但它并不完美。这非常复杂。我不知道anyone实际上在生产中这样做,我当然不推荐它。
也就是说,我一直在玩以异步递归锁为例 https://nitoasyncex.codeplex.com/SourceControl/changeset/view/cfd83ac30725#Source/AdvancedExamples/RecursiveAsyncLockExample.cs在我的 AsyncEx 库中(它永远不会成为公共 API 的一部分)。您可以像这样使用它(遵循已取消令牌同步操作的 AsyncEx 约定 http://blog.stephencleary.com/2013/04/cancellationtoken-behavior-with-asyncex.html):
class Resource
{
private readonly RecursiveAsyncLock mutex = new RecursiveAsyncLock();
public RecursiveLockAsync.RecursiveLockAwaitable LockAsync(bool immediate = false)
{
if (immediate)
return mutex.LockAsync(new CancellationToken(true));
return mutex.LockAsync();
}
}
async void button1Click(..)
{
using (r.LockAsync(true))
{
... // mess with r
await _IndependentResourceModiferAsync();
}
}
async void button2Click(..)
{
using (r.LockAsync(true))
{
await _IndependentResourceModiferAsync();
}
}
async Task _IndependentResourceModiferAsync()
{
using (await r.LockAsync())
{
...
}
}
代码为RecursiveAsyncLock
篇幅虽然不长,但是想想就让人心旷神怡。它始于隐式异步上下文 http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html我在我的博客上详细描述了它(它本身很难理解),然后使用自定义等待在正确的时间向最终用户“注入”代码async
方法。
你正处于任何人都尝试过的边缘。RecursiveAsyncLock
根本没有经过彻底的测试,而且很可能永远不会。
小心行事,探险家。这里有龙。