47

我有一些问题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>
    }

当我在 <1> 处输入写锁时,在调试器中我可以看到那_indexLock.IsWriteLockHeldtrue,但是当执行到 <2> 的步骤时,我看到_indexLock.IsWriteLockHeld的是false_indexLock.ExitWriteLock抛出异常SynchronizationLockException消息“写锁正在被释放而不被持有”。我做错了什么?

4

5 回答 5

76

ReaderWriterLockSlim是线程仿射锁类型,因此通常不能与asyncand一起使用await

您应该使用SemaphoreSlimwith WaitAsync,或者(如果您真的需要读/写锁),使用AsyncReaderWriterLock来自 AsyncExStephen Toub 的AsyncReaderWriterLockmy 。

于 2013-10-29T16:51:11.203 回答
5

您可以使用可靠和轻量级的方式安全地模拟读写器锁定机制,SemaphoreSlim并保留async/的好处await。创建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)
    }
}

显然,这种方法只有在您事先知道可以同时运行多少个阅读程序时才有效。诚然,它们太多会使这段代码变得非常难看。

于 2015-06-29T16:41:30.207 回答
3

前段时间我为我的项目类 AsyncReaderWriterLock 实现了基于两个 SemaphoreSlim。希望它可以提供帮助。它实现了相同的逻辑(多读取器和单写入器),同时支持异步/等待模式。当然,它不支持递归,也没有防止错误使用的保护:

var rwLock = new AsyncReaderWriterLock();

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

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


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)
        {
            try
            {
                await SafeAcquireReadSemaphore(token).ConfigureAwait(false);
            }
            catch
            {
                Interlocked.Decrement(ref _readerCount);

                throw;
            }
        }

        _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();
    }
}
于 2020-11-09T18:41:12.553 回答
2

https://docs.microsoft.com/en-us/dotnet/api/system.threading.readerwriterlockslim?view=net-5.0

从来源:

ReaderWriterLockSlim 具有托管线程亲和性;也就是说,每个 Thread 对象必须进行自己的方法调用才能进入和退出锁定模式。没有线程可以改变另一个线程的模式。

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

最好使用其他答案中的其他锁定机制,例如SemaphoreSlim.

于 2021-01-12T07:30:40.600 回答
0

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


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

请求读卡器时:

  • 是否有激活的 writerlock?
  • 有什么东西已经排队了吗?(我认为按照请求的顺序执行它很好)

请求 writerlock 时:

  • 是否有激活的 writerlock?(因为 writerlocks 不应该并行执行)
  • 是否有激活的读卡器锁?
  • 有什么东西已经排队了吗?

如果这些条件中的任何一个回答yes,则应将执行排队并稍后执行。您可以使用TaskCompletionSources继续awaits

当任何读取器或写入器完成时,您应该评估队列并尽可能继续执行项目。


例如(nuget)AsyncReaderWriterLock

于 2020-12-06T23:42:13.560 回答