C# ReaderWriterLockSlim和async\Wait

C# ReaderWriterLockSlim和async\Wait,c#,asynchronous,locking,C#,Asynchronous,Locking,我对ReaderWriterLockSlim有一些问题。我不明白它是怎么变魔术的 我的代码: private async Task LoadIndex() { if (!File.Exists(FileName + ".index.txt")) { return; } _indexLock.EnterWriteLock();// <1> _index.Clear();

我对ReaderWriterLockSlim有一些问题。我不明白它是怎么变魔术的

我的代码:

 private async Task LoadIndex()
    {
        if (!File.Exists(FileName + ".index.txt"))
        {
            return;
        }
        _indexLock.EnterWriteLock();// <1>
        _index.Clear();
        using (TextReader index = File.OpenText(FileName + ".index.txt"))
        {
            string s;
            while (null != (s = await index.ReadLineAsync()))
            {
                var ss = s.Split(':');
                _index.Add(ss[0], Convert.ToInt64(ss[1]));
            }
        }
        _indexLock.ExitWriteLock();<2>
    }
private async Task LoadIndex()
{
如果(!File.Exists(FileName+“.index.txt”))
{
返回;
}
_indexLock.EnterWriteLock();//
_index.Clear();
使用(TextReader index=File.OpenText(FileName+“.index.txt”))
{
字符串s;
while(null!=(s=await index.readlinesync())
{
var ss=s.Split(“:”);
_index.Add(ss[0],Convert.ToInt64(ss[1]);
}
}
_ExitWriteLock();
}
在调试器中输入write lock at时,我可以看到
\u indexLock.iswritelockhold
true
,但当执行步骤到时,我看到
\u indexLock.iswritelockhold
false

\u indexLock.ExitWriteLock
抛出异常
SynchronizationLockException
,并显示消息“正在释放写锁而不保持”。我做错了什么?

ReaderWriterLockSlim
是一种线程仿射锁类型,因此它通常不能与
异步
等待
一起使用


您应该将
SemaphoreSlim
WaitAsync
一起使用,或者(如果您真的需要读写器锁),使用my or。

您可以使用可靠且轻量级的
SemaphoreSlim
安全地模拟读写器锁定机制,并保持
异步的优点。创建
SemaphoreSlim
,为其提供与锁定资源以同时读取的例程数量相等的可用锁数量。每个人都会像往常一样申请一把锁。对于您的写入例程,确保它在执行操作之前请求所有可用的锁。

这样,您的写入例程将始终单独运行,而您的读取例程可能只在它们之间共享资源。

例如,假设您有2个读取例程和1个写入例程

SemaphoreSlim semaphore = new SemaphoreSlim(2);

async void Reader1()
{
    await semaphore.WaitAsync();
    try
    {
        // ... reading stuff ...
    }
    finally
    {
        semaphore.Release();
    }
}

async void Reader2()
{
    await semaphore.WaitAsync();
    try
    {
        // ... reading other stuff ...
    }
    finally
    {
        semaphore.Release();
    }
}

async void ExclusiveWriter()
{
    // the exclusive writer must request all locks
    // to make sure the readers don't have any of them
    // (I wish we could specify the number of locks
    // instead of spamming multiple calls!)
    await semaphore.WaitAsync();
    await semaphore.WaitAsync();
    try
    {
        // ... writing stuff ...
    }
    finally
    {
        // release all locks here
        semaphore.Release(2);
        // (oh here we don't need multiple calls, how about that)
    }
}

显然,只有事先知道可以同时运行多少个读取例程,这种方法才有效。不可否认,它们太多会使这段代码变得非常丑陋。

不久前,我基于两个信号量lim为我的项目类AsyncReaderWriterLock实现了。希望能有所帮助。它采用相同的逻辑(多个读卡器和单个写卡器)实现,同时支持异步/等待模式。当然,它不支持递归,也没有防止错误使用的保护:

var rwLock = new AsyncReaderWriterLock();

await rwLock.AcquireReaderLock();
try
{
    // ... reading ...
}
finally
{
    rwLock.ReleaseReaderLock();
}

await rwLock.AcquireWriterLock();
try
{
    // ... writing ...
}
finally
{
    rwLock.ReleaseWriterLock();
}



正如Stephen Cleary所说,ReaderWriterLockSlim是一种线程仿射锁类型,因此它通常不能与async和await一起使用


您必须建立一种机制来避免读写器同时访问共享数据。这个算法应该遵循一些规则

请求readerlock时:

  • 是否有一个writerlock处于活动状态
  • 有什么东西已经排队了吗?(我认为按照请求的顺序执行它很好)
请求writerlock时:

  • 是否有一个writerlock处于活动状态?(因为writerlock不应该并行执行)
  • 是否有任何读卡器锁处于活动状态
  • 有什么东西已经排队了吗
如果这些creteria中的任何一个回答为“是”,则执行应排队,稍后再执行。您可以使用继续
等待

完成任何读写器后,应评估队列并在可能的情况下继续执行项


例如(nuget):

来源:

ReaderWriterLockSlim已管理线程关联;也就是说,每个线程 对象必须进行自己的方法调用才能进入和退出锁定模式。不 线程可以更改另一个线程的模式

所以这里是预期行为。Async/await不保证在同一个线程中继续,所以当您在一个线程中输入写锁并尝试在另一个线程中退出时,可以捕获异常


最好使用其他答案中的其他锁定机制,如
信号量lim

如何初始化
\u indexLock
?另一个线程是否可以在另一个线程位于
LoadIndex()
的同时对其进行初始化?可能另一个有权访问_indexLock的线程正在重新初始化它。。。。它肯定不能被另一个线程释放,但可能会一起重新初始化为一个新实例……它不需要一个线程来获取要覆盖的_indexLock。_indexLock在类istance创建和声明时被初始化,如下所示:private readonly readerwriterlocksim _indexLock=new readerwriterlocksim()。没有一个线程可以修改它。这不提供读/写语义。读写器锁的概念是,可以有任意数量的并发读写器,但当写操作正在进行时,就不能有任何读写器或其他写入器。这既不必要地限制了读卡器,也不能在一个操作正在写入时正确地限制读卡器或其他写入器。当您有活动读卡器时,我会阻止任何写入器,当您没有任何活动写入器时,它仍然允许并发读卡器。这基本上就是读写器锁应该做的,除非你不知道你会有多少个读写器,正如我明确指出的(如果你真的想一想,即使这样也可以做到,但我不会破坏你的惊喜)。对于您的标准来说,它也可能不那么优雅,但它是一个有效的解决方案,我自己在经过良好测试、压力很大的高需求服务中使用了它。我只想简单一点,但每个都有自己的特点……这对于一个不依赖外部依赖的快速解决方案来说并不坏。如果需要的话,可能应该包装到它自己的类中以供重用(为每次读取调用多个
.WaitAsync()
,并记住您容纳了多少个读卡器)
public sealed class AsyncReaderWriterLock : IDisposable
{
    private readonly SemaphoreSlim _readSemaphore  = new SemaphoreSlim(1, 1);
    private readonly SemaphoreSlim _writeSemaphore = new SemaphoreSlim(1, 1);
    private          int           _readerCount;

    public async Task AcquireWriterLock(CancellationToken token = default)
    {
        await _writeSemaphore.WaitAsync(token).ConfigureAwait(false);
        await SafeAcquireReadSemaphore(token).ConfigureAwait(false);
    }

    public void ReleaseWriterLock()
    {
        _readSemaphore.Release();
        _writeSemaphore.Release();
    }

    public async Task AcquireReaderLock(CancellationToken token = default)
    {
        await _writeSemaphore.WaitAsync(token).ConfigureAwait(false);

        if (Interlocked.Increment(ref _readerCount) == 1)
        {
            await SafeAcquireReadSemaphore(token).ConfigureAwait(false);
        }

        _writeSemaphore.Release();
    }

    public void ReleaseReaderLock()
    {
        if (Interlocked.Decrement(ref _readerCount) == 0)
        {
            _readSemaphore.Release();
        }
    }

    private async Task SafeAcquireReadSemaphore(CancellationToken token)
    {
        try
        {
            await _readSemaphore.WaitAsync(token).ConfigureAwait(false);
        }
        catch
        {
            _writeSemaphore.Release();

            throw;
        }
    }

    public void Dispose()
    {
        _writeSemaphore.Dispose();
        _readSemaphore.Dispose();
    }
}