-2
对于执行异步文件IO,我制作了一个类,让我根据字符串键进行锁定,以防止同时对同一文件进行多次写入,或者防止写入和读取同时完成。然而,我面临的问题是,通过每次发送一个不同的密钥来增加_lockDict的可能性。旧的未使用的锁没有被清理,但我不知道如何以线程安全的方式做到这一点。这可能会导致非常大的内存消耗。清理NamedReaderWriterLocker中未使用的锁
,让背部基于钥匙的锁的实例类:
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最感兴趣的是这个l赚钱的目的,并充分认识到这对大多数应用程序来说是矫枉过正的。
您需要添加一种方法将锁标记为未使用。这可能会在任何客户端发送文件名时完成。一旦决定不再需要文件名,它可以告诉你的'AsyncNamedReaderWriterLocker'转储锁。这就是说,同一个角色可以很容易地协调谁读写什么文件,这样就不需要并发文件访问。为了获得更具体的内容,准确地知道你要完成的是什么,以及为什么你需要并发的文件访问而不是异步访问? – JSteward
随着异步的事情也会出现并发。您可以尝试同时读取和写入同一个文件,导致并发性。这就是为什么需要锁来防止同时写入和读取同一个文件的原因。此代码将用于服务器中以提供文件和一些其他逻辑,例如生成图像缩略图。我正在寻找的是一旦不再使用锁(在线程中),锁将被清除。 – Barsonax
你将不得不跟踪有多少线程持有或等待着锁(你的锁似乎已经有这个信息),然后当一个锁被释放时,如果没有其他线程在等待,就从你的字典。当然,这将需要在发布步骤进行额外的同步,以确保在您释放它的过程中没有线程检索到字典中的锁。我怀疑这比仅仅对所有资源使用一个锁定更好(尤其是考虑到所有I/O操作瓶颈的可能性)。 –