为了执行异步文件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;
}
}
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最初对此有兴趣用于学习目的,并且完全意识到这对于大多数应用来说都是过度的。
答案 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();
}
}
}