ReaderWriterLockSlim и async\ждут

У меня проблемы с 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.IsWriteLockHeld true, но когда шаги выполнения для <2> я вижу _indexLock.IsWriteLockHeld является false и _indexLock.ExitWriteLock выдает исключение SynchronizationLockException с сообщением "Запись блокировка освобождается без удержания ". Что я делаю неправильно?

Ответ 1

ReaderWriterLockSlim является поточно-аффинным типом блокировки, поэтому его обычно нельзя использовать с async и await.

Вы должны либо использовать SemaphoreSlim с WaitAsync, либо (если вам действительно нужна блокировка чтения/записи), используйте мой AsyncReaderWriterLock от AsyncEx или Stephen AsyncReaderWriterLock.

Ответ 2

Вы можете безопасно эмулировать механизм блокировки чтения/записи с использованием надежного и легкого 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)
    }
}

Очевидно, что этот метод работает, только если вы заранее знаете, сколько программ чтения вы могли бы запустить одновременно. По общему признанию, слишком многие из них сделают этот код очень уродливым.