ReaderWriterLockSlim和async \ await

时间:2013-10-29 13:20:15

标签: c# asynchronous locking

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>
    }

当我在&lt; 1&gt;处输入写锁定时,在调试器中我可以看到_indexLock.IsWriteLockHeldtrue,但是当执行步骤为&lt; 2&gt;时我看到_indexLock.IsWriteLockHeldfalse 并且_indexLock.ExitWriteLock抛出异常SynchronizationLockException,并显示消息“正在释放写锁定但未被保留”。我做错了什么?

5 个答案:

答案 0 :(得分:39)

ReaderWriterLockSlim是一种线程仿射锁类型,因此通常无法与asyncawait一起使用。

您应该将SemaphoreSlimWaitAsync一起使用,或者(如果确实需要读者/作者锁定),请使用我的AsyncReaderWriterLock from AsyncEx或{{3 }}

答案 1 :(得分:4)

您可以使用可靠且轻量级的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)
    }
}

显然,只有在事先知道可以同时运行多少个阅读程序时,此方法才有效。不可否认,他们中的太多会使这段代码非常丑陋。

答案 2 :(得分:1)

前一段时间,我基于两个SemaphoreSlim为我的项目类AsyncReaderWriterLock实现了。希望能有所帮助。它实现了相同的逻辑(多个读取器和单个写入器),并且同时支持异步/等待模式。肯定,它不支持递归并且不能防止错误使用:

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)
        {
            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();
    }
}

答案 3 :(得分:1)

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

来源:

<块引用>

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

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

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

答案 4 :(得分:0)

就像史蒂芬·克莱里(Stephen Cleary)所说,ReaderWriterLockSlim是线程仿射锁类型,因此通常不能与异步和等待一起使用。


您必须建立一种机制来避免读者和作家同时访问共享数据。该算法应遵循一些规则。

请求读者锁时:

  • 有写锁吗?
  • 是否有任何已排队的东西? (我认为按请求的顺序执行它很好)

请求作家锁时:

  • 是否有活跃的写锁? (因为writerlocks不应并行执行)
  • 是否有任何阅读器锁处于活动状态?
  • 有什么已经排队吗?

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

完成任何读取器或写入器后,您应该评估队列并在可能的情况下继续执行项目。


例如(nuget)AsyncReaderWriterLock