清理NamedReaderWriterLocker中未使用的锁

时间:2017-04-04 21:07:41

标签: c# multithreading asynchronous thread-safety locking

为了执行异步文件IO,我创建了一个类,它允许我基于字符串键进行锁定,以防止同时对同一文件进行多次写入,或者防止同时进行写入和读取。然而,我面临的问题是,每次都可以通过发送不同的密钥来增长_lockDict。旧的未使用的锁没有清理,但我不知道如何以线程安全的方式做到这一点。这可能会导致非常大的内存消耗。

基于密钥返回锁定实例的类:

    public class AsyncNamedReaderWriterLocker
{
    private readonly object _mutex = new object();
    private readonly Dictionary<string, AsyncReaderWriterLock> _lockDict = new Dictionary<string, AsyncReaderWriterLock>();

    public Task<IDisposable> EnterReaderLockAsync(string name)
    {
        var locker = GetLock(name);
        return locker.EnterReaderLockAsync();
    }

    public Task<IDisposable> EnterWriterLockAsync(string name)
    {
        var locker = GetLock(name);
        return locker.EnterWriterLockAsync();
    }

    private AsyncReaderWriterLock GetLock(string name)
    {
        lock (_mutex)
        {
            if (!_lockDict.TryGetValue(name, out AsyncReaderWriterLock locker))
            {
                locker = new AsyncReaderWriterLock();
                _lockDict.Add(name, locker);
            }
            return locker;
        }
    }

锁定本身(想法来自:https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-7-asyncreaderwriterlock/):

   public class AsyncReaderWriterLock
{
    private readonly Queue<TaskCompletionSource<IDisposable>> _writerQueue = new Queue<TaskCompletionSource<IDisposable>>();
    private readonly Queue<TaskCompletionSource<IDisposable>> _readerQueue = new Queue<TaskCompletionSource<IDisposable>>();
    private readonly WriterLocker _writerLocker;
    private readonly ReaderLocker _readerLocker;
    private readonly object _mutex = new object();
    private int _locksHeld;

    public AsyncReaderWriterLock()
    {
        _writerLocker = new WriterLocker(this);
        _readerLocker = new ReaderLocker(this);
    }

    public Task<IDisposable> EnterReaderLockAsync()
    {
        lock (_mutex)
        {
            if (_locksHeld >= 0 && _writerQueue.Count == 0)
            {
                _locksHeld++;
                return Task.FromResult<IDisposable>(_readerLocker);
            }
            var tcs = new TaskCompletionSource<IDisposable>();
            _readerQueue.Enqueue(tcs);
            return tcs.Task;
        }
    }

    public Task<IDisposable> EnterWriterLockAsync()
    {
        lock (_mutex)
        {
            if (_locksHeld == 0)
            {
                _locksHeld = -1;
                return Task.FromResult<IDisposable>(_writerLocker);
            }
            var tcs = new TaskCompletionSource<IDisposable>();
            _writerQueue.Enqueue(tcs);
            return tcs.Task;
        }
    }

    private void ReleaseLocks()
    {
        if (_locksHeld != 0)
            return;

        // Give priority to writers.
        if (_writerQueue.Count != 0)
        {
            _locksHeld = -1;
            var tcs = _writerQueue.Dequeue();
            tcs.TrySetResult(_writerLocker);
            return;
        }

        // Then to readers.
        while (_readerQueue.Count != 0)
        {
            var tcs = _readerQueue.Dequeue();
            tcs.TrySetResult(_readerLocker);
            ++_locksHeld;
        }
    }

    private void ReleaseReaderLock()
    {
        lock (_mutex)
        {
            _locksHeld--;
            ReleaseLocks();
        }
    }

    private void ReleaseWriterLock()
    {
        lock (_mutex)
        {
            _locksHeld = 0;
            ReleaseLocks();
        }
    }

    private class ReaderLocker : IDisposable
    {
        private readonly AsyncReaderWriterLock _asyncReaderWriterLock;

        internal ReaderLocker(AsyncReaderWriterLock asyncReaderWriterLock)
        {
            _asyncReaderWriterLock = asyncReaderWriterLock;
        }

        public void Dispose()
        {
            _asyncReaderWriterLock.ReleaseReaderLock();
        }
    }

    private class WriterLocker : IDisposable
    {
        private readonly AsyncReaderWriterLock _asyncReaderWriterLock;

        internal WriterLocker(AsyncReaderWriterLock asyncReaderWriterLock)
        {
            _asyncReaderWriterLock = asyncReaderWriterLock;
        }

        public void Dispose()
        {
            _asyncReaderWriterLock.ReleaseWriterLock();
        }
    }
}

使用AsyncNamedReaderWriterLocker类的示例:

    public class AsyncFileIO
{
    private static readonly AsyncNamedReaderWriterLocker AsyncNamedReaderWriterLocker = new AsyncNamedReaderWriterLocker();

    public async Task CreateFile(string filename, byte[] data)
    {
        using (await AsyncNamedReaderWriterLocker.EnterWriterLockAsync(filename))
        {
            var directory = Path.GetDirectoryName(filename);
            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }
            using (var stream = File.Create(filename))
            {
                await stream.WriteAsync(data, 0, data.Length);
            }
        }
    }

    public async Task<byte[]> ReadFile(string filename)
    {
        using (await AsyncNamedReaderWriterLocker.EnterReaderLockAsync(filename))
        {
            if (!File.Exists(filename)) return null;
            using (var stream = File.OpenRead(filename))
            {
                var data = new byte[stream.Length];
                await stream.ReadAsync(data, 0, 0);
                return data;
            }
        }
    }

    public async Task DeleteFile(string filename)
    {
        using (await AsyncNamedReaderWriterLocker.EnterWriterLockAsync(filename))
        {
            var directoryName = Path.GetDirectoryName(filename);
            if (!Directory.Exists(directoryName)) return;
            File.Delete(filename);
        }
    }
}

注意iam最初对此有兴趣用于学习目的,并且完全意识到这对于大多数应用来说都是过度的。

1 个答案:

答案 0 :(得分:0)

首先,当尝试同步异步命名锁以删除未使用的锁时,我遇到了死锁。这是因为并非所有锁都以相同的顺序发布。修复此问题需要一个专门的AsyncReaderWriter锁,只是为了与这个异步命名锁一起使用。它现在看起来像这样:

    public class NamedAsyncReaderWriterLockController<TKey>
{
    private readonly object _mutex = new object();
    private readonly Dictionary<TKey, NamedAsyncReaderWriterLock> _lockDict = new Dictionary<TKey, NamedAsyncReaderWriterLock>();


    public Task<IDisposable> EnterReaderLockAsync(TKey name)
    {
        lock (_mutex)
        {
            var locker = GetLock(name);
            return locker.EnterReaderLockAsync();
        }
    }

    public Task<IDisposable> EnterWriterLockAsync(TKey name)
    {
        lock (_mutex)
        {
            var locker = GetLock(name);
            return locker.EnterWriterLockAsync();
        }
    }

    private NamedAsyncReaderWriterLock GetLock(TKey name)
    {
        NamedAsyncReaderWriterLock locker;
        if (!_lockDict.TryGetValue(name, out locker))
        {
            locker = new NamedAsyncReaderWriterLock(this, name, _mutex);
            _lockDict.Add(name, locker);
        }
        return locker;
    }

    private void RemoveLock(TKey name)
    {
        _lockDict.Remove(name);
    }

    private class NamedAsyncReaderWriterLock
    {
        private readonly TKey _name;
        private readonly NamedAsyncReaderWriterLockController<TKey> _namedAsyncReaderWriterLockController;
        private readonly Queue<TaskCompletionSource<IDisposable>> _writerQueue = new Queue<TaskCompletionSource<IDisposable>>();
        private readonly Queue<TaskCompletionSource<IDisposable>> _readerQueue = new Queue<TaskCompletionSource<IDisposable>>();
        private readonly NamedWriterLock _namedWriterLock;
        private readonly NamedReaderLock _namedReaderLock;
        private readonly object _mutex = new object();
        private readonly object _releaseMutex;
        private int _locksHeld;

        public NamedAsyncReaderWriterLock(NamedAsyncReaderWriterLockController<TKey> namedAsyncReaderWriterLockController, TKey name, object releaseMutex)
        {
            _namedWriterLock = new NamedWriterLock(this);
            _namedReaderLock = new NamedReaderLock(this);
            _releaseMutex = releaseMutex;
            _name = name;
            _namedAsyncReaderWriterLockController = namedAsyncReaderWriterLockController;
        }

        public Task<IDisposable> EnterReaderLockAsync()
        {
            lock (_mutex)
            {
                if (_locksHeld >= 0 && _writerQueue.Count == 0)
                {
                    _locksHeld++;
                    return Task.FromResult<IDisposable>(_namedReaderLock);
                }
                var tcs = new TaskCompletionSource<IDisposable>();
                _readerQueue.Enqueue(tcs);
                return tcs.Task;
            }
        }

        public Task<IDisposable> EnterWriterLockAsync()
        {
            lock (_mutex)
            {
                if (_locksHeld == 0)
                {
                    _locksHeld = -1;
                    return Task.FromResult<IDisposable>(_namedWriterLock);
                }
                var tcs = new TaskCompletionSource<IDisposable>();
                _writerQueue.Enqueue(tcs);
                return tcs.Task;
            }
        }

        private void ReleaseLocks()
        {
            if (_locksHeld != 0)
                return;

            // Give priority to writers.
            if (_writerQueue.Count != 0)
            {
                _locksHeld = -1;
                var tcs = _writerQueue.Dequeue();
                tcs.TrySetResult(_namedWriterLock);
                return;
            }

            // Then to readers.
            while (_readerQueue.Count != 0)
            {
                var tcs = _readerQueue.Dequeue();
                tcs.TrySetResult(_namedReaderLock);
                ++_locksHeld;
            }

            if (_locksHeld == 0) _namedAsyncReaderWriterLockController.RemoveLock(_name);
        }

        private void ReleaseReaderLock()
        {
            lock (_releaseMutex)
            {
                lock (_mutex)
                {
                    _locksHeld--;
                    ReleaseLocks();
                }
            }
        }

        private void ReleaseWriterLock()
        {
            lock (_releaseMutex)
            {
                lock (_mutex)
                {
                    _locksHeld = 0;
                    ReleaseLocks();
                }
            }
        }

        private class NamedReaderLock : IDisposable
        {
            private readonly NamedAsyncReaderWriterLock _namedAsyncReaderWriterLock;

            internal NamedReaderLock(NamedAsyncReaderWriterLock namedAsyncReaderWriterLock)
            {
                _namedAsyncReaderWriterLock = namedAsyncReaderWriterLock;
            }

            public void Dispose()
            {
                _namedAsyncReaderWriterLock.ReleaseReaderLock();
            }
        }

        private class NamedWriterLock : IDisposable
        {
            private readonly NamedAsyncReaderWriterLock _namedAsyncReaderWriterLock;

            internal NamedWriterLock(NamedAsyncReaderWriterLock namedAsyncReaderWriterLock)
            {
                _namedAsyncReaderWriterLock = namedAsyncReaderWriterLock;
            }

            public void Dispose()
            {
                _namedAsyncReaderWriterLock.ReleaseWriterLock();
            }
        }
    }