是否有办法防止在编译时在异步或非单线程上下文中调用方法?

问题描述 投票:0回答:1

我发现它有时非常有用 - 主要是为了避免过度缩进,但偶尔也用于其他目的 - 能够使用

Monitor.Enter
Monitor.Exit
而不是
lock
关键字,嵌入到
IDisposable 的实现中
.

但是,这会带来一个简单的错误,据我所知,C# 在没有帮助的情况下无法在编译时捕获该错误 - 也就是说,如果您在异步方法中不恰当地使用此技术,该方法可能会在不同的代码上运行不同的代码块线程,不能保证

Monitor.Exit
将在与
Monitor.Enter
相同的线程上被调用。

由于这是我自己的代码,到目前为止,我已经用“你能做的那件愚蠢的事情?不要这样做”的历史悠久的技术回避了这个问题,但我想要一些更好的导轨,如果它们'重新可用。我想到的是类似一个属性,我可以将它放在一个方法上,基本上说“不允许在异步代码块中调用此方法”。

有这样的事吗?

请注意,我不想被指出信号量 - 问题不在于解决方案的选择,而在于我是否可以添加导轨,以便程序无法编译,或者 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 thread #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, and myriad other issues
}

c# multithreading async-await locking
1个回答
2
投票

据我所知,编译器中没有任何具体内容旨在检测这一点。但是,您可能会编写自己的分析器:我怀疑您可能会在这里滥用

ref struct
ref struct
无法放入
async
状态机,因为它不能脱离堆栈。如果您制作
public ref struct LockHelper
(请注意,您无法在
IDisposable
上显式实现
ref struct
,但
using
仍然可以通过
public void Dispose()
工作):这可能足以满足您的需求。

© www.soinside.com 2019 - 2024. All rights reserved.