我发现,有时候它是非常有用的—主要是为了避免过度缩进,但偶尔也是为了其他目的—能够使用Monitor.EnterMonitor.Exit而不是嵌入在IDisposable实现中的lock关键字.

然而,这会引发一个简单的bug,据我所知,C#在没有帮助的情况下无法在编译时捕捉到——也就是说,如果你在一个可能在不同线程上运行不同代码块的Systemc方法中不适当地使用了这种技术,就不能保证Monitor.Exit会在同一线程上被调用.

由于这是我自己的代码,到目前为止,我已经采取了一种由来已久的技术,即"你能做的愚蠢的事情?"不要这样做",但我想要一些更好的导轨,如果他们可用.我想到的是一个类似于属性的东西,我可以把它放在一个方法上,它基本上说:"当在一个javoc代码块中时,不允许调用这个方法".

这样的东西存在吗?

请注意,我并不希望被指向信号量—问题不是解决方案的 Select ,而是我是否可以添加指南,以使程序无法编译,或者LockHelper构造函数或方法在这种情况下可能由于疏忽而出现时会失败.

问题的例子:

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 structref struct不能放入async状态机,因为它不能离开堆栈.如果您使用public ref struct LockHelper(注意,您不能在ref struct上显式实现IDisposable,但using仍然可以通过public void Dispose()工作):这可能足以满足您的需求.

Csharp相关问答推荐

为什么C#Bigbit不总是相同的比特长度?

为什么我的ASP.NET核心MVC应用程序要为HTML元素添加一些标识符?

是否可以使用EF—Core进行临时部分更新?

将XPS转换为PDF C#

碰撞检测与盒碰撞器,其isTrigger on问题

默认情况下,.NET通用主机(Host.CreateDefaultBuilder)中是否包含UseConsoleLifetime?

StackExchange.Redis.RedisServerException:调用ITransaction.ExecuteAsync()时出现错误未知命令取消监视

如何将此方法参数化并使其更灵活?

如何在ASP.NET Core8中启用REST应用程序的序列化?

在swagger示例中添加默认数组列表

在IAsyncEnumerable上先调用,然后跳过(1)可以吗?

从VS调试器而不是测试资源管理器运行的调试NUnitDotNet测试

我可以查看我们向应用程序洞察发送了多少数据吗?

Maui:更改代码中的绑定字符串不会更新UI,除非重新生成字符串(MVVM)

如何正确地在VB中初始化类?

使用ITfoxtec.Identity.Saml2解析相同键多值SAML 2声明

与另一个对象位于同一位置的对象具有不同的变换位置

反编译源代码时出现奇怪的字符

这是T自身的布尔表达式是什么意思?

ASP.NET核心中的验证错误-该字段为必填字段