Skip to content

Commit 62be3a1

Browse files
CopilotSoar360
andcommitted
Optimize thread-safety: use FileShare.Read instead of locks
Co-authored-by: Soar360 <[email protected]>
1 parent bdd6da5 commit 62be3a1

File tree

1 file changed

+18
-121
lines changed

1 file changed

+18
-121
lines changed

LuYao.ResourcePacker/ResourcePackageReader.cs

Lines changed: 18 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ namespace LuYao.ResourcePacker
88
{
99
/// <summary>
1010
/// Provides functionality to read resources from a packaged resource file.
11-
/// This class is thread-safe for concurrent read operations.
11+
/// This class is thread-safe for concurrent read operations by creating independent FileStream instances per operation.
1212
/// </summary>
1313
public class ResourcePackageReader : IDisposable
1414
{
15-
private readonly FileStream _fileStream;
15+
private readonly string _filePath;
1616
private readonly Dictionary<string, ResourceEntry> _resourceIndex;
17-
private readonly object _lock = new object();
1817
private bool _disposed;
1918

2019
/// <summary>
@@ -23,14 +22,15 @@ public class ResourcePackageReader : IDisposable
2322
/// <param name="filePath">The path to the resource package file.</param>
2423
public ResourcePackageReader(string filePath)
2524
{
26-
_fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
25+
_filePath = filePath ?? throw new ArgumentNullException(nameof(filePath));
2726
_resourceIndex = new Dictionary<string, ResourceEntry>();
2827
LoadIndex();
2928
}
3029

3130
private void LoadIndex()
3231
{
33-
using var reader = new BinaryReader(_fileStream, System.Text.Encoding.UTF8, leaveOpen: true);
32+
using var fileStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
33+
using var reader = new BinaryReader(fileStream, System.Text.Encoding.UTF8, leaveOpen: false);
3434

3535
// Read version number
3636
var version = reader.ReadByte();
@@ -52,7 +52,7 @@ private void LoadIndex()
5252
}
5353

5454
// Calculate offsets based on the current position
55-
long currentOffset = _fileStream.Position;
55+
long currentOffset = fileStream.Position;
5656
foreach (var (key, length) in indexEntries)
5757
{
5858
_resourceIndex[key] = new ResourceEntry
@@ -94,15 +94,15 @@ public Task<byte[]> ReadResourceAsync(string resourceKey)
9494

9595
var buffer = new byte[entry.Length];
9696

97-
// Lock to ensure thread-safe access to FileStream
98-
lock (_lock)
97+
// Create a new FileStream for this read operation (thread-safe via FileShare.Read)
98+
using (var fileStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
9999
{
100-
_fileStream.Seek(entry.Offset, SeekOrigin.Begin);
100+
fileStream.Seek(entry.Offset, SeekOrigin.Begin);
101101

102102
int totalRead = 0;
103103
while (totalRead < entry.Length)
104104
{
105-
int bytesRead = _fileStream.Read(buffer, totalRead, entry.Length - totalRead);
105+
int bytesRead = fileStream.Read(buffer, totalRead, entry.Length - totalRead);
106106
if (bytesRead == 0)
107107
throw new EndOfStreamException($"Unexpected end of stream while reading resource '{resourceKey}'.");
108108
totalRead += bytesRead;
@@ -150,15 +150,15 @@ public byte[] ReadResource(string resourceKey)
150150

151151
var buffer = new byte[entry.Length];
152152

153-
// Lock to ensure thread-safe access to FileStream
154-
lock (_lock)
153+
// Create a new FileStream for this read operation (thread-safe via FileShare.Read)
154+
using (var fileStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
155155
{
156-
_fileStream.Seek(entry.Offset, SeekOrigin.Begin);
156+
fileStream.Seek(entry.Offset, SeekOrigin.Begin);
157157

158158
int totalRead = 0;
159159
while (totalRead < entry.Length)
160160
{
161-
int bytesRead = _fileStream.Read(buffer, totalRead, entry.Length - totalRead);
161+
int bytesRead = fileStream.Read(buffer, totalRead, entry.Length - totalRead);
162162
if (bytesRead == 0)
163163
throw new EndOfStreamException($"Unexpected end of stream while reading resource '{resourceKey}'.");
164164
totalRead += bytesRead;
@@ -204,20 +204,17 @@ public Stream GetStream(string resourceKey)
204204
if (!_resourceIndex.TryGetValue(resourceKey, out var entry))
205205
throw new KeyNotFoundException($"Resource with key '{resourceKey}' not found.");
206206

207-
// Create a SubStream wrapper that provides a read-only view of a portion of the file
208-
return new SubStream(_fileStream, entry.Offset, entry.Length, _lock);
207+
// Read the resource data into memory and return a MemoryStream
208+
var buffer = ReadResource(resourceKey);
209+
return new MemoryStream(buffer, writable: false);
209210
}
210211

211212
/// <summary>
212213
/// Releases the resources used by the <see cref="ResourcePackageReader"/>.
213214
/// </summary>
214215
public void Dispose()
215216
{
216-
if (!_disposed)
217-
{
218-
_fileStream?.Dispose();
219-
_disposed = true;
220-
}
217+
_disposed = true;
221218
}
222219
}
223220

@@ -226,104 +223,4 @@ internal class ResourceEntry
226223
public long Offset { get; set; }
227224
public int Length { get; set; }
228225
}
229-
230-
/// <summary>
231-
/// A stream wrapper that provides a read-only view of a portion of another stream.
232-
/// This class is thread-safe when used with a lock object.
233-
/// </summary>
234-
internal class SubStream : Stream
235-
{
236-
private readonly Stream _baseStream;
237-
private readonly long _offset;
238-
private readonly long _length;
239-
private readonly object _lock;
240-
private long _position;
241-
242-
public SubStream(Stream baseStream, long offset, long length, object lockObject)
243-
{
244-
_baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
245-
_lock = lockObject ?? throw new ArgumentNullException(nameof(lockObject));
246-
_offset = offset;
247-
_length = length;
248-
_position = 0;
249-
}
250-
251-
public override bool CanRead => true;
252-
public override bool CanSeek => true;
253-
public override bool CanWrite => false;
254-
public override long Length => _length;
255-
256-
public override long Position
257-
{
258-
get => _position;
259-
set
260-
{
261-
if (value < 0 || value > _length)
262-
throw new ArgumentOutOfRangeException(nameof(value));
263-
_position = value;
264-
}
265-
}
266-
267-
public override int Read(byte[] buffer, int offset, int count)
268-
{
269-
if (buffer == null)
270-
throw new ArgumentNullException(nameof(buffer));
271-
if (offset < 0)
272-
throw new ArgumentOutOfRangeException(nameof(offset));
273-
if (count < 0)
274-
throw new ArgumentOutOfRangeException(nameof(count));
275-
if (buffer.Length - offset < count)
276-
throw new ArgumentException("Invalid offset/count combination");
277-
278-
long remaining = _length - _position;
279-
if (remaining <= 0)
280-
return 0;
281-
282-
int toRead = (int)Math.Min(count, remaining);
283-
int bytesRead;
284-
285-
// Lock to ensure thread-safe access to the base stream
286-
lock (_lock)
287-
{
288-
_baseStream.Seek(_offset + _position, SeekOrigin.Begin);
289-
bytesRead = _baseStream.Read(buffer, offset, toRead);
290-
}
291-
292-
_position += bytesRead;
293-
294-
return bytesRead;
295-
}
296-
297-
public override long Seek(long offset, SeekOrigin origin)
298-
{
299-
long newPosition = origin switch
300-
{
301-
SeekOrigin.Begin => offset,
302-
SeekOrigin.Current => _position + offset,
303-
SeekOrigin.End => _length + offset,
304-
_ => throw new ArgumentException("Invalid seek origin", nameof(origin))
305-
};
306-
307-
if (newPosition < 0 || newPosition > _length)
308-
throw new ArgumentOutOfRangeException(nameof(offset));
309-
310-
_position = newPosition;
311-
return _position;
312-
}
313-
314-
public override void Flush()
315-
{
316-
// Read-only stream, nothing to flush
317-
}
318-
319-
public override void SetLength(long value)
320-
{
321-
throw new NotSupportedException("Cannot set length on a read-only stream.");
322-
}
323-
324-
public override void Write(byte[] buffer, int offset, int count)
325-
{
326-
throw new NotSupportedException("Cannot write to a read-only stream.");
327-
}
328-
}
329226
}

0 commit comments

Comments
 (0)