diff --git a/docs/API.md b/docs/API.md
index b6df93d9..295feeda 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -694,6 +694,8 @@ using (var archive = ZipArchive.OpenArchive("archive.zip", options))
### Async Extract with Cancellation
+Async archive and reader/writer operations dispose SharpCompress resources asynchronously on every target, including .NET Framework 4.8 and .NET Standard 2.0. `SharpCompressStream.DisposeAsync()` and `ZlibStream.DisposeAsync()` are available on every target; stream wrappers forward disposal to runtime `IAsyncDisposable` implementations where available, falling back to synchronous disposal only for streams that do not support it.
+
```csharp
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromMinutes(5));
diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs b/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs
index 3b6ee24d..032e33f3 100644
--- a/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs
+++ b/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs
@@ -25,10 +25,11 @@ public partial class GZipArchive
CancellationToken cancellationToken = default
)
{
-#if !STREAM_DISPOSEASYNC
+#if SYNC_ONLY
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
#else
- await using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
+ var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
+ await using var scope = stream.DisposeAsyncScope().ConfigureAwait(false);
#endif
await SaveToAsync(stream, new GZipWriterOptions(CompressionType.GZip), cancellationToken)
.ConfigureAwait(false);
@@ -47,24 +48,23 @@ public partial class GZipArchive
{
throw new InvalidFormatException("Only one entry is allowed in a GZip Archive");
}
-#if !STREAM_DISPOSEASYNC
- using var writer = new GZipWriter(stream, options);
-#else
await using var writer = new GZipWriter(stream, options);
-#endif
await foreach (
var entry in oldEntries.WithCancellation(cancellationToken).ConfigureAwait(false)
)
{
if (!entry.IsDirectory)
{
-#if !STREAM_DISPOSEASYNC
- using var entryStream = await entry
+#if SYNC_ONLY
+ using var entryStream = entry.OpenEntryStream();
#else
- await using var entryStream = await entry
-#endif
+ var entryStream = await entry
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);
+ await using var entryStreamScope = entryStream
+ .DisposeAsyncScope()
+ .ConfigureAwait(false);
+#endif
await writer
.WriteAsync(
entry.Key.NotNull("Entry Key is null"),
@@ -77,13 +77,16 @@ public partial class GZipArchive
}
foreach (var entry in newEntries.Where(x => !x.IsDirectory))
{
-#if !STREAM_DISPOSEASYNC
- using var entryStream = await entry
+#if SYNC_ONLY
+ using var entryStream = entry.OpenEntryStream();
#else
- await using var entryStream = await entry
-#endif
+ var entryStream = await entry
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);
+ await using var entryStreamScope = entryStream
+ .DisposeAsyncScope()
+ .ConfigureAwait(false);
+#endif
await writer
.WriteAsync(
entry.Key.NotNull("Entry Key is null"),
diff --git a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs
index 98ab3115..b4d858f2 100644
--- a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs
+++ b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs
@@ -77,13 +77,9 @@ public static partial class IArchiveEntryExtensions
var entryStream = await archiveEntry
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);
-#if !STREAM_DISPOSEASYNC
- using var entryStreamScope = entryStream;
-#else
await using var entryStreamScope = entryStream
.DisposeAsyncScope()
.ConfigureAwait(false);
-#endif
#endif
var checkedStream = options is null
? entryStream
@@ -168,10 +164,11 @@ public static partial class IArchiveEntryExtensions
options,
async (x, fm, ct) =>
{
-#if !STREAM_DISPOSEASYNC
+#if SYNC_ONLY
using var fs = File.Open(x, fm);
#else
- await using var fs = File.Open(x, fm);
+ var fs = File.Open(x, fm);
+ await using var scope = fs.DisposeAsyncScope().ConfigureAwait(false);
#endif
await entry
.WriteToAsync(fs, options.BufferSize, options, null, ct)
diff --git a/src/SharpCompress/Archives/IWritableAsyncArchiveExtensions.cs b/src/SharpCompress/Archives/IWritableAsyncArchiveExtensions.cs
index 7e32f1d2..d74b756b 100644
--- a/src/SharpCompress/Archives/IWritableAsyncArchiveExtensions.cs
+++ b/src/SharpCompress/Archives/IWritableAsyncArchiveExtensions.cs
@@ -83,11 +83,8 @@ public static class IWritableAsyncArchiveExtensions
)
where TOptions : IWriterOptions
{
-#if !STREAM_DISPOSEASYNC
- using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
-#else
- await using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
-#endif
+ var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
+ await using var scope = stream.DisposeAsyncScope().ConfigureAwait(false);
await writableArchive.SaveToAsync(stream, options, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/src/SharpCompress/Archives/Tar/TarArchive.Async.cs b/src/SharpCompress/Archives/Tar/TarArchive.Async.cs
index a318ab53..2c5e69e0 100644
--- a/src/SharpCompress/Archives/Tar/TarArchive.Async.cs
+++ b/src/SharpCompress/Archives/Tar/TarArchive.Async.cs
@@ -22,11 +22,7 @@ public partial class TarArchive
CancellationToken cancellationToken = default
)
{
-#if !STREAM_DISPOSEASYNC
- using var writer = new TarWriter(stream, options);
-#else
await using var writer = new TarWriter(stream, options);
-#endif
await foreach (
var entry in oldEntries.WithCancellation(cancellationToken).ConfigureAwait(false)
)
@@ -43,13 +39,12 @@ public partial class TarArchive
}
else
{
-#if !STREAM_DISPOSEASYNC
- using var entryStream = await entry
-#else
- await using var entryStream = await entry
-#endif
+ var entryStream = await entry
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);
+ await using var entryStreamScope = entryStream
+ .DisposeAsyncScope()
+ .ConfigureAwait(false);
await writer
.WriteAsync(
entry.Key.NotNull("Entry Key is null"),
@@ -75,15 +70,12 @@ public partial class TarArchive
}
else
{
-#if !STREAM_DISPOSEASYNC
- using var entryStream = await entry
+ var entryStream = await entry
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);
-#else
- await using var entryStream = await entry
- .OpenEntryStreamAsync(cancellationToken)
+ await using var entryStreamScope = entryStream
+ .DisposeAsyncScope()
.ConfigureAwait(false);
-#endif
await writer
.WriteAsync(
entry.Key.NotNull("Entry Key is null"),
diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.Async.cs b/src/SharpCompress/Archives/Zip/ZipArchive.Async.cs
index a65719a0..122112eb 100644
--- a/src/SharpCompress/Archives/Zip/ZipArchive.Async.cs
+++ b/src/SharpCompress/Archives/Zip/ZipArchive.Async.cs
@@ -84,11 +84,7 @@ public partial class ZipArchive
CancellationToken cancellationToken = default
)
{
-#if !STREAM_DISPOSEASYNC
- using var writer = new ZipWriter(stream, options);
-#else
await using var writer = new ZipWriter(stream, options);
-#endif
await foreach (
var entry in oldEntries.WithCancellation(cancellationToken).ConfigureAwait(false)
)
@@ -105,13 +101,12 @@ public partial class ZipArchive
}
else
{
-#if !STREAM_DISPOSEASYNC
- using var entryStream = await entry
-#else
- await using var entryStream = await entry
-#endif
+ var entryStream = await entry
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);
+ await using var entryStreamScope = entryStream
+ .DisposeAsyncScope()
+ .ConfigureAwait(false);
await writer
.WriteAsync(
entry.Key.NotNull("Entry Key is null"),
@@ -135,13 +130,12 @@ public partial class ZipArchive
}
else
{
-#if !STREAM_DISPOSEASYNC
- using var entryStream = await entry
-#else
- await using var entryStream = await entry
-#endif
+ var entryStream = await entry
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);
+ await using var entryStreamScope = entryStream
+ .DisposeAsyncScope()
+ .ConfigureAwait(false);
await writer
.WriteAsync(
entry.Key.NotNull("Entry Key is null"),
diff --git a/src/SharpCompress/Common/Rar/Headers/MarkHeader.Async.cs b/src/SharpCompress/Common/Rar/Headers/MarkHeader.Async.cs
index 5aae4c3c..52b0ad4f 100644
--- a/src/SharpCompress/Common/Rar/Headers/MarkHeader.Async.cs
+++ b/src/SharpCompress/Common/Rar/Headers/MarkHeader.Async.cs
@@ -122,11 +122,7 @@ internal partial class MarkHeader
{
if (!leaveStreamOpen)
{
-#if !STREAM_DISPOSEASYNC
- stream.Dispose();
-#else
- await stream.DisposeAsync().ConfigureAwait(false);
-#endif
+ await stream.DisposeAsyncCompat().ConfigureAwait(false);
}
throw new InvalidFormatException("Error trying to read rar signature.", e);
}
diff --git a/src/SharpCompress/Common/SevenZip/SevenZipStreamsCompressor.cs b/src/SharpCompress/Common/SevenZip/SevenZipStreamsCompressor.cs
index e769ea23..1b704e0c 100644
--- a/src/SharpCompress/Common/SevenZip/SevenZipStreamsCompressor.cs
+++ b/src/SharpCompress/Common/SevenZip/SevenZipStreamsCompressor.cs
@@ -122,11 +122,7 @@ internal sealed class SevenZipStreamsCompressor(Stream outputStream)
uint inputCrc2;
long inputSize2;
{
-#if !STREAM_DISPOSEASYNC
- using var lzma2Stream = new Lzma2EncoderStream(
-#else
await using var lzma2Stream = new Lzma2EncoderStream(
-#endif
outCrcStream,
encoderProperties.DictionarySize,
encoderProperties.NumFastBytes
@@ -156,11 +152,7 @@ internal sealed class SevenZipStreamsCompressor(Stream outputStream)
uint inputCrc;
long inputSize;
{
-#if !STREAM_DISPOSEASYNC
- using var lzmaStream = LzmaStream.Create(encoderProperties, false, outCrcStream);
-#else
await using var lzmaStream = LzmaStream.Create(encoderProperties, false, outCrcStream);
-#endif
properties = lzmaStream.Properties;
(inputCrc, inputSize) = await CopyWithCrcAsync(
diff --git a/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs b/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs
index ced9ea27..1d0608a2 100644
--- a/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs
+++ b/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs
@@ -1,10 +1,11 @@
using System;
using System.IO;
using System.Threading.Tasks;
+using SharpCompress.IO;
namespace SharpCompress.Common.Tar;
-internal class TarReadOnlySubStream : Stream
+internal class TarReadOnlySubStream : AsyncDisposableStream
{
private readonly Stream _stream;
@@ -34,7 +35,6 @@ internal class TarReadOnlySubStream : Stream
base.Dispose(disposing);
}
-#if STREAM_DISPOSEASYNC
public override async System.Threading.Tasks.ValueTask DisposeAsync()
{
if (_isDisposed)
@@ -49,7 +49,6 @@ internal class TarReadOnlySubStream : Stream
GC.SuppressFinalize(this);
await base.DisposeAsync().ConfigureAwait(false);
}
-#endif
private long BytesLeftToRead { get; set; }
diff --git a/src/SharpCompress/Common/Volume.Async.cs b/src/SharpCompress/Common/Volume.Async.cs
index 55d9b686..05984162 100644
--- a/src/SharpCompress/Common/Volume.Async.cs
+++ b/src/SharpCompress/Common/Volume.Async.cs
@@ -7,12 +7,7 @@ public abstract partial class Volume
{
public virtual async ValueTask DisposeAsync()
{
-#if !STREAM_DISPOSEASYNC
- _actualStream.Dispose();
- await Task.CompletedTask.ConfigureAwait(false);
-#else
- await _actualStream.DisposeAsync().ConfigureAwait(false);
-#endif
+ await _actualStream.DisposeAsyncCompat().ConfigureAwait(false);
GC.SuppressFinalize(this);
}
}
diff --git a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs
index e9867989..242b8515 100644
--- a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs
+++ b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs
@@ -10,7 +10,6 @@ namespace SharpCompress.Common.Zip;
internal partial class WinzipAesCryptoStream
{
-#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
{
if (_isDisposed)
@@ -31,11 +30,10 @@ internal partial class WinzipAesCryptoStream
_authentication.Dispose();
_transform.Dispose();
_cipher.Dispose();
- await _stream.DisposeAsync().ConfigureAwait(false);
+ await _stream.DisposeAsyncCompat().ConfigureAwait(false);
}
await base.DisposeAsync().ConfigureAwait(false);
}
-#endif
private async ValueTask VerifyAuthenticationAsync(CancellationToken cancellationToken)
{
diff --git a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs
index 06b354af..b96ca1eb 100644
--- a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs
+++ b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs
@@ -3,10 +3,11 @@ using System.Buffers.Binary;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Security.Cryptography;
+using SharpCompress.IO;
namespace SharpCompress.Common.Zip;
-internal partial class WinzipAesCryptoStream : Stream
+internal partial class WinzipAesCryptoStream : AsyncDisposableStream
{
private const int BLOCK_SIZE_IN_BYTES = 16;
private readonly Aes _cipher;
diff --git a/src/SharpCompress/Compressors/Deflate/ZlibStream.Async.cs b/src/SharpCompress/Compressors/Deflate/ZlibStream.Async.cs
index 8e433214..1021bbde 100644
--- a/src/SharpCompress/Compressors/Deflate/ZlibStream.Async.cs
+++ b/src/SharpCompress/Compressors/Deflate/ZlibStream.Async.cs
@@ -23,6 +23,9 @@ public partial class ZlibStream
#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
+#else
+ public virtual async ValueTask DisposeAsync()
+#endif
{
if (_disposed)
{
@@ -33,9 +36,13 @@ public partial class ZlibStream
{
await _baseStream.DisposeAsync().ConfigureAwait(false);
}
+#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
- }
+#else
+ base.Dispose();
+ GC.SuppressFinalize(this);
#endif
+ }
///
/// Read data from the stream.
diff --git a/src/SharpCompress/Compressors/Deflate/ZlibStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibStream.cs
index 580961d7..97c77538 100644
--- a/src/SharpCompress/Compressors/Deflate/ZlibStream.cs
+++ b/src/SharpCompress/Compressors/Deflate/ZlibStream.cs
@@ -33,6 +33,9 @@ using SharpCompress.Common;
namespace SharpCompress.Compressors.Deflate;
public partial class ZlibStream : Stream
+#if !STREAM_DISPOSEASYNC
+ , IAsyncDisposable
+#endif
{
private readonly ZlibBaseStream _baseStream;
private bool _disposed;
diff --git a/src/SharpCompress/Compressors/PPMd/PpmdStream.cs b/src/SharpCompress/Compressors/PPMd/PpmdStream.cs
index d6a97a61..bcf39c90 100644
--- a/src/SharpCompress/Compressors/PPMd/PpmdStream.cs
+++ b/src/SharpCompress/Compressors/PPMd/PpmdStream.cs
@@ -160,11 +160,7 @@ public class PpmdStream : Stream, IAsyncDisposable
}
catch
{
-#if !STREAM_DISPOSEASYNC
- instance.Dispose();
-#else
await instance.DisposeAsync().ConfigureAwait(false);
-#endif
throw;
}
}
diff --git a/src/SharpCompress/IO/ProgressReportingStream.Async.cs b/src/SharpCompress/IO/ProgressReportingStream.Async.cs
index 449990f5..096c0a95 100644
--- a/src/SharpCompress/IO/ProgressReportingStream.Async.cs
+++ b/src/SharpCompress/IO/ProgressReportingStream.Async.cs
@@ -45,14 +45,12 @@ internal sealed partial class ProgressReportingStream
}
#endif
-#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
{
if (!_leaveOpen)
{
- await _baseStream.DisposeAsync().ConfigureAwait(false);
+ await _baseStream.DisposeAsyncCompat().ConfigureAwait(false);
}
await base.DisposeAsync().ConfigureAwait(false);
}
-#endif
}
diff --git a/src/SharpCompress/IO/ProgressReportingStream.cs b/src/SharpCompress/IO/ProgressReportingStream.cs
index 8834a546..4eb73bab 100644
--- a/src/SharpCompress/IO/ProgressReportingStream.cs
+++ b/src/SharpCompress/IO/ProgressReportingStream.cs
@@ -8,7 +8,7 @@ namespace SharpCompress.IO;
/// A stream wrapper that reports progress as data is read from the source.
/// Used to track compression or extraction progress by wrapping the source stream.
///
-internal sealed partial class ProgressReportingStream : Stream
+internal sealed partial class ProgressReportingStream : AsyncDisposableStream
{
private readonly Stream _baseStream;
private readonly IProgress _progress;
diff --git a/src/SharpCompress/IO/SeekableSharpCompressStream.Async.cs b/src/SharpCompress/IO/SeekableSharpCompressStream.Async.cs
index dd082d39..7bf2b650 100644
--- a/src/SharpCompress/IO/SeekableSharpCompressStream.Async.cs
+++ b/src/SharpCompress/IO/SeekableSharpCompressStream.Async.cs
@@ -30,12 +30,12 @@ internal sealed partial class SeekableSharpCompressStream
) => _stream.WriteAsync(buffer, cancellationToken);
#endif
-#if STREAM_DISPOSEASYNC
- public override ValueTask DisposeAsync()
+ public override async ValueTask DisposeAsync()
{
if (_isDisposed)
{
- return base.DisposeAsync();
+ await base.DisposeAsync().ConfigureAwait(false);
+ return;
}
if (ThrowOnDispose)
{
@@ -46,11 +46,10 @@ internal sealed partial class SeekableSharpCompressStream
_isDisposed = true;
if (!LeaveStreamOpen)
{
- _stream.Dispose();
+ await _stream.DisposeAsyncCompat().ConfigureAwait(false);
}
- return base.DisposeAsync();
+ await base.DisposeAsync().ConfigureAwait(false);
}
-#endif
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override Task WriteAsync(
diff --git a/src/SharpCompress/IO/SharpCompressStream.Async.cs b/src/SharpCompress/IO/SharpCompressStream.Async.cs
index 2b2057d7..47ba651e 100644
--- a/src/SharpCompress/IO/SharpCompressStream.Async.cs
+++ b/src/SharpCompress/IO/SharpCompressStream.Async.cs
@@ -253,6 +253,9 @@ public partial class SharpCompressStream
#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
+#else
+ public virtual async ValueTask DisposeAsync()
+#endif
{
if (!isDisposed)
{
@@ -267,7 +270,7 @@ public partial class SharpCompressStream
{
if (!LeaveStreamOpen)
{
- await stream.DisposeAsync().ConfigureAwait(false);
+ await stream.DisposeAsyncCompat().ConfigureAwait(false);
}
}
finally
@@ -276,7 +279,11 @@ public partial class SharpCompressStream
_ringBuffer = null;
}
}
+#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
- }
+#else
+ base.Dispose();
+ GC.SuppressFinalize(this);
#endif
+ }
}
diff --git a/src/SharpCompress/IO/SharpCompressStream.cs b/src/SharpCompress/IO/SharpCompressStream.cs
index 7a5b7777..d59c53c2 100644
--- a/src/SharpCompress/IO/SharpCompressStream.cs
+++ b/src/SharpCompress/IO/SharpCompressStream.cs
@@ -17,6 +17,9 @@ namespace SharpCompress.IO;
/// stream and usage scenario.
///
public partial class SharpCompressStream : Stream, IStreamStack
+#if !STREAM_DISPOSEASYNC
+ , IAsyncDisposable
+#endif
{
public virtual Stream BaseStream() => stream;
diff --git a/src/SharpCompress/Readers/AbstractReader.Async.cs b/src/SharpCompress/Readers/AbstractReader.Async.cs
index 7b75cac2..969c73e6 100644
--- a/src/SharpCompress/Readers/AbstractReader.Async.cs
+++ b/src/SharpCompress/Readers/AbstractReader.Async.cs
@@ -104,11 +104,7 @@ public abstract partial class AbstractReader
}
}
//don't know the size so we have to try to decompress to skip
-#if !STREAM_DISPOSEASYNC
- using var s = await OpenEntryStreamAsync(cancellationToken).ConfigureAwait(false);
-#else
await using var s = await OpenEntryStreamAsync(cancellationToken).ConfigureAwait(false);
-#endif
await s.SkipEntryAsync(cancellationToken).ConfigureAwait(false);
}
@@ -138,11 +134,7 @@ public abstract partial class AbstractReader
private async ValueTask WriteAsync(Stream writeStream, CancellationToken cancellationToken)
{
-#if !STREAM_DISPOSEASYNC
- using var s = await OpenEntryStreamAsync(cancellationToken).ConfigureAwait(false);
-#else
await using var s = await OpenEntryStreamAsync(cancellationToken).ConfigureAwait(false);
-#endif
var sourceStream = WrapWithProgress(s, Entry);
await sourceStream
.CopyToAsync(writeStream, Options.BufferSize, cancellationToken)
diff --git a/src/SharpCompress/Readers/IAsyncReaderExtensions.cs b/src/SharpCompress/Readers/IAsyncReaderExtensions.cs
index 12473db3..c962d116 100644
--- a/src/SharpCompress/Readers/IAsyncReaderExtensions.cs
+++ b/src/SharpCompress/Readers/IAsyncReaderExtensions.cs
@@ -45,11 +45,8 @@ public static class IAsyncReaderExtensions
options,
async (x, fm, ct) =>
{
-#if !STREAM_DISPOSEASYNC
- using var fs = File.Open(x, fm);
-#else
- await using var fs = File.Open(x, fm);
-#endif
+ var fs = File.Open(x, fm);
+ await using var scope = fs.DisposeAsyncScope().ConfigureAwait(false);
await CopyEntryToAsync(reader, fs, options, ct).ConfigureAwait(false);
},
cancellationToken
@@ -87,11 +84,8 @@ public static class IAsyncReaderExtensions
options,
async (x, fm, ct) =>
{
-#if !STREAM_DISPOSEASYNC
- using var fs = File.Open(x, fm);
-#else
- await using var fs = File.Open(x, fm);
-#endif
+ var fs = File.Open(x, fm);
+ await using var scope = fs.DisposeAsyncScope().ConfigureAwait(false);
await CopyEntryToAsync(reader, fs, options, ct).ConfigureAwait(false);
},
cancellationToken
@@ -116,11 +110,7 @@ public static class IAsyncReaderExtensions
CancellationToken cancellationToken
)
{
-#if !STREAM_DISPOSEASYNC
- using var entryStream = await reader
-#else
await using var entryStream = await reader
-#endif
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);
var checkedStream = IEntryExtensions.WrapWithChecksumValidation(
diff --git a/src/SharpCompress/Utility.Async.cs b/src/SharpCompress/Utility.Async.cs
index 7d1546c5..6e6debff 100644
--- a/src/SharpCompress/Utility.Async.cs
+++ b/src/SharpCompress/Utility.Async.cs
@@ -70,11 +70,8 @@ internal static partial class Utility
)
{
// Use ReadOnlySubStream to limit reading and leverage framework's CopyToAsync
-#if !STREAM_DISPOSEASYNC
- using var limitedStream = new IO.ReadOnlySubStream(source, maxLength);
-#else
- await using var limitedStream = new IO.ReadOnlySubStream(source, maxLength);
-#endif
+ var limitedStream = new IO.ReadOnlySubStream(source, maxLength);
+ await using var scope = limitedStream.DisposeAsyncScope().ConfigureAwait(false);
await limitedStream
.CopyToAsync(destination, bufferSize ?? Constants.BufferSize, cancellationToken)
.ConfigureAwait(false);
diff --git a/src/SharpCompress/Writers/IWriterExtensions.cs b/src/SharpCompress/Writers/IWriterExtensions.cs
index d6c94ea0..b1a082f9 100644
--- a/src/SharpCompress/Writers/IWriterExtensions.cs
+++ b/src/SharpCompress/Writers/IWriterExtensions.cs
@@ -77,11 +77,8 @@ public static class IWriterExtensions
{
throw new ArgumentException("Source does not exist: " + source.FullName);
}
-#if !STREAM_DISPOSEASYNC
- using var stream = source.OpenRead();
-#else
- await using var stream = source.OpenRead();
-#endif
+ var stream = source.OpenRead();
+ await using var scope = stream.DisposeAsyncScope().ConfigureAwait(false);
await writer
.WriteAsync(entryPath, stream, source.LastWriteTime, cancellationToken)
.ConfigureAwait(false);
diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.Async.cs b/src/SharpCompress/Writers/Zip/ZipWriter.Async.cs
index 0f3bea53..ec0d9ca4 100644
--- a/src/SharpCompress/Writers/Zip/ZipWriter.Async.cs
+++ b/src/SharpCompress/Writers/Zip/ZipWriter.Async.cs
@@ -83,11 +83,7 @@ public partial class ZipWriter
#if !SYNC_ONLY
cancellationToken.ThrowIfCancellationRequested();
#endif
-#if !STREAM_DISPOSEASYNC
- using var output = await WriteToStreamAsync(
-#else
await using var output = await WriteToStreamAsync(
-#endif
entryPath,
zipWriterEntryOptions,
cancellationToken
diff --git a/tests/SharpCompress.Test/Streams/AsyncDisposalCompatibilityTests.cs b/tests/SharpCompress.Test/Streams/AsyncDisposalCompatibilityTests.cs
new file mode 100644
index 00000000..6407eda8
--- /dev/null
+++ b/tests/SharpCompress.Test/Streams/AsyncDisposalCompatibilityTests.cs
@@ -0,0 +1,53 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using SharpCompress.IO;
+using SharpCompress.Test.Mocks;
+using Xunit;
+
+namespace SharpCompress.Test.Streams;
+
+public class AsyncDisposalCompatibilityTests
+{
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async ValueTask SharpCompressStream_Forwards_Async_Disposal(bool seekable)
+ {
+ var source = new AsyncDisposeOnlyStream(seekable);
+ var wrapper = SharpCompressStream.Create(source, 128);
+ await ((IAsyncDisposable)wrapper).DisposeAsync().ConfigureAwait(false);
+ Assert.True(source.AsyncDisposed);
+ await ((IAsyncDisposable)wrapper).DisposeAsync().ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async ValueTask Stream_Disposal_Scope_Uses_Runtime_Async_Disposable()
+ {
+ var source = new AsyncDisposeOnlyStream(true);
+ Stream stream = source;
+ await using (stream.DisposeAsyncScope().ConfigureAwait(false)) { }
+ Assert.True(source.AsyncDisposed);
+ }
+
+ private sealed class AsyncDisposeOnlyStream(bool seekable) : AsyncOnlyStream(new MemoryStream())
+ {
+ public override bool CanSeek => seekable;
+ public bool AsyncDisposed { get; private set; }
+
+ public override ValueTask DisposeAsync()
+ {
+ AsyncDisposed = true;
+ return base.DisposeAsync();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !AsyncDisposed)
+ {
+ throw new NotSupportedException("Synchronous disposal is not supported.");
+ }
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/tests/SharpCompress.Test/Streams/WinzipAesCryptoStreamTests.cs b/tests/SharpCompress.Test/Streams/WinzipAesCryptoStreamTests.cs
index 861eaf20..463b49f0 100644
--- a/tests/SharpCompress.Test/Streams/WinzipAesCryptoStreamTests.cs
+++ b/tests/SharpCompress.Test/Streams/WinzipAesCryptoStreamTests.cs
@@ -25,12 +25,6 @@ public class WinzipAesCryptoStreamTests
{
foreach (var completion in new[] { "dispose", "retry", "dispose-async" })
{
-#if LEGACY_DOTNET
- if (completion == "dispose-async")
- {
- continue;
- }
-#endif
yield return [length, tagBytesRead, corruption, completion];
}
}
@@ -82,12 +76,10 @@ public class WinzipAesCryptoStreamTests
{
stream.Dispose();
}
-#if STREAM_DISPOSEASYNC
else if (completion == "dispose-async")
{
await stream.DisposeAsync();
}
-#endif
else
{
Assert.Equal(0, await stream.ReadAsync(new byte[1], 0, 1, CancellationToken.None));
@@ -153,7 +145,6 @@ public class WinzipAesCryptoStreamTests
stream.Dispose();
}
-#if STREAM_DISPOSEASYNC
[Fact]
public async Task DisposeAsync_Verifies_Unread_Ciphertext()
{
@@ -171,7 +162,6 @@ public class WinzipAesCryptoStreamTests
);
await stream.DisposeAsync();
}
-#endif
[Theory]
[InlineData(false, 0)]
diff --git a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs
index afa668ed..f31d6a84 100644
--- a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs
+++ b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs
@@ -286,7 +286,6 @@ public class TarArchiveAsyncTests : ArchiveTests
Assert.Equal(0, opened.Length);
}
-#if STREAM_DISPOSEASYNC
[Fact]
public async ValueTask Tar_Save_New_Entry_Async_Only_Streams()
{
@@ -308,11 +307,11 @@ public class TarArchiveAsyncTests : ArchiveTests
destination.Position = 0;
await using var reopened = await TarArchive.OpenAsyncArchive(destination);
var entry = await reopened.EntriesAsync.SingleAsync();
- await using var stream = await entry.OpenEntryStreamAsync(CancellationToken.None);
+ var stream = await entry.OpenEntryStreamAsync(CancellationToken.None);
+ await using var scope = stream.DisposeAsyncScope().ConfigureAwait(false);
using var reader = new StreamReader(stream);
Assert.Equal("test", await reader.ReadToEndAsync());
}
-#endif
[Fact]
public async ValueTask Tar_Random_Write_Add_Async()
diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs
index be8c0bc8..710a4141 100644
--- a/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs
+++ b/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs
@@ -265,7 +265,6 @@ public class ZipArchiveAsyncTests : ArchiveTests
Assert.Equal(0, opened.Length);
}
-#if STREAM_DISPOSEASYNC
[Fact]
public async ValueTask Zip_Save_New_Entry_Async_Only_Streams()
{
@@ -284,11 +283,11 @@ public class ZipArchiveAsyncTests : ArchiveTests
destination.Position = 0;
await using var reopened = await ZipArchive.OpenAsyncArchive(destination);
var entry = await reopened.EntriesAsync.SingleAsync();
- await using var stream = await entry.OpenEntryStreamAsync(CancellationToken.None);
+ var stream = await entry.OpenEntryStreamAsync(CancellationToken.None);
+ await using var scope = stream.DisposeAsyncScope().ConfigureAwait(false);
using var reader = new StreamReader(stream);
Assert.Equal("test", await reader.ReadToEndAsync());
}
-#endif
[Fact]
public async ValueTask Zip_Deflate_Entry_Stream_Async()