我发现它有时非常有用 - 主要是为了避免过度缩进,但偶尔也用于其他目的 - 能够使用
Monitor.Enter
和 Monitor.Exit
而不是 lock
关键字,嵌入到 IDisposable
的实现中
.
但是,这会带来一个简单的错误,据我所知,C# 在没有帮助的情况下无法在编译时捕获该错误 - 也就是说,如果您在异步方法中不恰当地使用此技术,该方法可能会在不同的代码上运行不同的代码块线程,不能保证
Monitor.Exit
将在与 Monitor.Enter
相同的线程上被调用。
由于这是我自己的代码,到目前为止,我已经用“你能做的那件愚蠢的事情?不要这样做”的历史悠久的技术回避了这个问题,但我想要一些更好的导轨,如果它们'重新可用。我想到的是类似一个属性,我可以将它放在一个方法上,基本上说“不允许在异步代码块中调用此方法”。
有这样的事吗?
请注意,我不想被指出信号量 - 问题不在于解决方案的选择,而在于我是否可以添加导轨,以便程序无法编译,或者 LockHelper 构造函数或方法在这种情况下会失败由于某些地方的疏忽,可能会出现这种情况。
问题示例:
public struct LockHelper : IDisposable
{
private readonly object _target;
private bool _locked;
private bool _disposed;
public LockHelper(object target, bool startLocked = true)
{
_target = target;
_locked = false;
_disposed = false;
if (startLocked)
Lock();
}
private void Lock()
{
AssertNotDisposed();
if (_locked)
return;
_locked = true;
Monitor.Enter(_target);
}
private void AssertNotDisposed()
{
if (!_disposed)
return;
throw new ObjectDisposedException(nameof(LockHelper));
}
private void Unlock()
{
AssertNotDisposed();
if (!_locked)
return;
_locked = false;
Monitor.Exit(_target);
}
public void Dispose()
{
if (_disposed)
return;
Unlock();
_disposed = true;
}
}
readonly object _sync = new object();
readonly IAsyncService _service;
async Task DoSomething()
{
// Start on threat #123
using var lh = new LockHelper(_sync);
await _service.SomeAsyncCall();
// Resume on some other threadpool thread, call it #456
// That means the _sync is still locked by #123, but we have NO WAY to unlock it
// Therefore disposal of #123 will happen on that other thread, myriad other issues
}
据我所知,编译器中没有任何具体内容旨在检测这一点。但是,您可能会编写自己的分析器:我怀疑您可能会在这里滥用
ref struct
; ref struct
无法放入 async
状态机,因为它不能脱离堆栈。如果您制作 public ref struct LockHelper
(请注意,您无法在 IDisposable
上显式实现 ref struct
,因此您需要手动 try
/finally
):这可能足以满足您的需求。