SharpCompress now provides full async/await support for all I/O operations. All Read, Write, and extraction operations have async equivalents ending in Async that accept an optional CancellationToken. This enables better performance and scalability for I/O-bound operations.
Key Async Methods:
reader.WriteEntryToAsync(stream, cancellationToken)- Extract entry asynchronouslyreader.WriteAllToDirectoryAsync(path, cancellationToken: cancellationToken)- Extract all asynchronouslywriter.WriteAsync(filename, stream, modTime, cancellationToken)- Write entry asynchronouslywriter.WriteAllAsync(directory, pattern, searchOption, cancellationToken)- Write directory asynchronouslyentry.OpenEntryStreamAsync(cancellationToken)- Open entry stream asynchronously
See Async Examples section below for usage patterns.
When dealing with Streams, the rule should be that you don't close a stream you didn't create. This, in effect, should mean you should always put a Stream in a using block to dispose it.
However, the .NET Framework often has classes that will dispose streams by default to make things "easy" like the following:
using (var reader = new StreamReader(File.Open("foo")))
{
...
}In this example, reader should get disposed. However, stream rules should say the the FileStream created by File.Open should remain open. However, the .NET Framework closes it for you by default unless you override the constructor. In general, you should be writing Stream code like this:
using (var fileStream = File.Open("foo"))
using (var reader = new StreamReader(fileStream))
{
...
}To deal with the "correct" rules as well as the expectations of users, I've decided to always close wrapped streams as of 0.21.
To be explicit though, consider always using the overloads that use ReaderOptions or WriterOptions and explicitly set LeaveStreamOpen the way you want.
Default behavior in factory APIs:
- File path /
FileInfooverloads setLeaveStreamOpen = false. - Caller-provided
Streamoverloads setLeaveStreamOpen = true.
If using Compression Stream classes directly and you don't want the wrapped stream to be closed. Use the NonDisposingStream as a wrapper to prevent the stream being disposed. The change in 0.21 simplified a lot even though the usage is a bit more convoluted.
Also, look over the tests for more thorough examples
using(var archive = ZipArchive.CreateArchive())
{
archive.AddEntry("file01.txt", "C:\\file01.txt");
archive.AddEntry("file02.txt", "C:\\file02.txt");
...
archive.SaveTo("C:\\temp.zip", CompressionType.Deflate);
}using (var archive = ZipArchive.CreateArchive())
{
archive.AddAllFromDirectory("D:\\temp");
archive.SaveTo("C:\\temp.zip", CompressionType.Deflate);
}var memoryStream = new MemoryStream();
using (var archive = ZipArchive.CreateArchive())
{
archive.AddAllFromDirectory("D:\\temp");
archive.SaveTo(memoryStream, new WriterOptions(CompressionType.Deflate)
{
LeaveStreamOpen = true
});
}
//reset memoryStream to be usable now
memoryStream.Position = 0;Note: Extracting a solid rar or 7z file needs to be done in sequential order to get acceptable decompression speed.
ExtractAllEntries is primarily intended for solid archives (like solid Rar) or 7Zip archives, where sequential extraction provides the best performance. For general/simple extraction with any supported archive type, use archive.WriteToDirectory() instead.
// Use ReaderOptions for open-time behavior and ExtractionOptions for extract-time behavior
using (var archive = RarArchive.OpenArchive("Test.rar", ReaderOptions.ForFilePath))
{
// Simple extraction with RarArchive; this WriteToDirectory pattern works for all archive types
archive.WriteToDirectory(
@"D:\temp",
new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = true,
BufferSize = 131072,
}
);
}using (var archive = RarArchive.OpenArchive("Test.rar"))
{
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
{
Console.WriteLine($"{entry.Key}: {entry.Size} bytes");
}
}For optimal performance with solid Rar and 7Zip archives, extract entries sequentially. WriteToDirectory handles that internally for simple extraction. Use ExtractAllEntries when you need to manually iterate in sequential order.
using SharpCompress.Common;
using SharpCompress.Readers;
var progress = new Progress<ProgressReport>(report =>
{
Console.WriteLine($"Extracting {report.EntryPath}: {report.PercentComplete}%");
});
using (var archive = RarArchive.OpenArchive("archive.rar",
ReaderOptions.ForFilePath
.WithProgress(progress)))
{
archive.WriteToDirectory(
@"D:\output",
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
}Manual sequential extraction:
using (var archive = RarArchive.OpenArchive("archive.rar",
ReaderOptions.ForFilePath.WithProgress(progress)))
using (var reader = archive.ExtractAllEntries())
{
while (reader.MoveToNextEntry())
{
if (!reader.Entry.IsDirectory)
{
reader.WriteEntryToDirectory(@"D:\output");
}
}
}using (Stream stream = File.OpenRead("Tar.tar.bz2"))
using (var reader = ReaderFactory.OpenReader(stream))
{
while (reader.MoveToNextEntry())
{
if (!reader.Entry.IsDirectory)
{
Console.WriteLine(reader.Entry.Key);
reader.WriteEntryToDirectory(@"C:\temp");
}
}
}using (Stream stream = File.OpenRead("Tar.tar.bz2"))
using (var reader = ReaderFactory.OpenReader(stream))
{
while (reader.MoveToNextEntry())
{
if (!reader.Entry.IsDirectory)
{
using (var entryStream = reader.OpenEntryStream())
{
entryStream.CopyTo(...);
}
}
}
}using (Stream stream = File.OpenWrite("C:\\temp.tgz"))
using (var writer = WriterFactory.OpenWriter(
stream,
ArchiveType.Tar,
WriterOptions.ForTar(CompressionType.GZip).WithLeaveStreamOpen(true)))
{
writer.WriteAll("D:\\temp", "*", SearchOption.AllDirectories);
}if (ArchiveFactory.IsArchive("archive.zip", out var archiveType))
{
Console.WriteLine($"Detected {archiveType}");
}
using (var archive = ArchiveFactory.OpenArchive("archive.zip"))
{
archive.WriteToDirectory(@"D:\output");
}var archivePath = "archive.arc";
var info = ArchiveFactory.GetArchiveInformation(archivePath);
if (info is null)
{
Console.WriteLine("Not a supported archive");
}
else if (info.SupportsRandomAccess)
{
using var archive = ArchiveFactory.OpenArchive(archivePath);
archive.WriteToDirectory(@"D:\output");
}
else
{
using var reader = ReaderFactory.OpenReader(archivePath);
reader.WriteAllToDirectory(@"D:\output");
}SupportsRandomAccess is false for reader-only formats such as Ace, Arc, Arj, and standalone LZW. Use the Reader API for those formats.
var parts = ArchiveFactory.GetFileParts("archive.part1.rar")
.Select(path => new FileInfo(path))
.ToArray();
using (var archive = ArchiveFactory.OpenArchive(parts))
{
archive.WriteToDirectory(@"D:\output");
}var sfxOptions = ReaderOptions.ForSelfExtractingArchive("password");
using (var archive = RarArchive.OpenArchive("setup.exe", sfxOptions))
{
archive.WriteToDirectory(@"D:\output");
}
using (Stream stream = File.OpenRead("backup"))
using (var reader = ReaderFactory.OpenReader(
stream,
ReaderOptions.ForExternalStream.WithExtensionHint("tar.gz")))
{
while (reader.MoveToNextEntry())
{
if (!reader.Entry.IsDirectory)
{
reader.WriteEntryToDirectory(@"D:\output");
}
}
}using Stream archiveStream = File.Create("output.zip");
using var writer = new ZipWriter(
archiveStream,
new ZipWriterOptions(CompressionType.Deflate)
{
ArchiveComment = "Archive comment",
UseZip64 = true,
});
using Stream source = File.OpenRead("input.txt");
writer.Write("entry.txt", source, new ZipWriterEntryOptions
{
CompressionType = CompressionType.ZStandard,
CompressionLevel = 3,
EntryComment = "Entry comment",
ModificationDateTime = DateTime.UtcNow,
EnableZip64 = true,
});using Stream stream = File.Create("output.7z");
using var writer = WriterFactory.OpenWriter(
stream,
ArchiveType.SevenZip,
new SevenZipWriterOptions(CompressionType.LZMA2)
{
CompressHeader = true,
});
using Stream source = File.OpenRead("input.txt");
writer.Write("input.txt", source, DateTime.UtcNow);var encoding = Encoding.GetEncoding(932);
var opts = new ReaderOptions()
.WithArchiveEncoding(new ArchiveEncoding
{
CustomDecoder = (data, x, y) => encoding.GetString(data)
});
using var archive = ZipArchive.OpenArchive("test.zip", opts);
foreach(var entry in archive.Entries)
{
Console.WriteLine($"{entry.Key}");
}By default ReaderOptions and WriterOptions already include CompressionProviderRegistry.Default via their Providers property, so you can read and write without touching the registry yet still get SharpCompress’s built-in implementations.
The configured registry is used consistently across Reader APIs, Writer APIs, Archive APIs, and async entry-stream extraction, including compressed TAR wrappers and ZIP async decompression.
To replace specific algorithms (for example to use System.IO.Compression for GZip or Deflate), create a modified registry and pass it through the same options:
var customRegistry = CompressionProviderRegistry.Default
.With(new SystemGZipCompressionProvider())
.With(new SystemDeflateCompressionProvider());
var readerOptions = ReaderOptions.ForFilePath
.WithProviders(customRegistry);
using var reader = ReaderFactory.OpenReader(stream, readerOptions);
var writerOptions = new WriterOptions(CompressionType.GZip)
.WithProviders(customRegistry);
using var writer = WriterFactory.OpenWriter(outputStream, ArchiveType.GZip, writerOptions);The registry is immutable. With(provider) returns a new registry and replaces any existing provider for that provider's CompressionType. You can inspect or use providers directly with GetProvider, CreateCompressStream, CreateDecompressStream, and their async/context overloads, but most application code should flow the registry through ReaderOptions or WriterOptions so archive readers and writers can supply the right CompressionContext.
To implement a custom provider, implement ICompressionProvider directly or derive from CompressionProviderBase for default async methods. Derive from DecompressionOnlyProviderBase for read-only codecs. Providers can use CompressionContext for stream size, seekability, reader options, compression properties, and format-specific metadata.
The registry also exposes GetCompressingProvider (now returning ICompressionProviderHooks) when a compression format needs pre- or post-stream data (e.g., LZMA/PPMd). Implementations that need extra headers can supply those bytes through the ICompressionProviderHooks members while the rest of the API still works through the Providers property.
Extract single entry asynchronously:
using Stream stream = File.OpenRead("archive.zip");
await using var reader = await ReaderFactory.OpenAsyncReader(stream, cancellationToken: cancellationToken);
while (await reader.MoveToNextEntryAsync(cancellationToken))
{
if (!reader.Entry.IsDirectory)
{
using var outputStream = File.Create("output.bin");
await reader.WriteEntryToAsync(outputStream, cancellationToken);
}
}Extract all entries asynchronously:
using Stream stream = File.OpenRead("archive.tar.gz");
await using var reader = await ReaderFactory.OpenAsyncReader(stream, cancellationToken: cancellationToken);
await reader.WriteAllToDirectoryAsync(
@"D:\temp",
cancellationToken: cancellationToken
);Open and process entry stream asynchronously:
await using var archive = await ZipArchive.OpenAsyncArchive("archive.zip", cancellationToken: cancellationToken);
await foreach (var entry in archive.EntriesAsync)
{
if (!entry.IsDirectory)
{
using var entryStream = await entry.OpenEntryStreamAsync(cancellationToken);
// Process the decompressed stream asynchronously
await ProcessStreamAsync(entryStream, cancellationToken);
}
}Write single file asynchronously:
using Stream archiveStream = File.OpenWrite("output.zip");
await using var writer = await WriterFactory.OpenAsyncWriter(archiveStream, ArchiveType.Zip, new WriterOptions(CompressionType.Deflate), cancellationToken);
using Stream fileStream = File.OpenRead("input.txt");
await writer.WriteAsync("entry.txt", fileStream, DateTime.Now, cancellationToken);Write entire directory asynchronously:
using Stream stream = File.OpenWrite("backup.tar.gz");
await using var writer = await WriterFactory.OpenAsyncWriter(stream, ArchiveType.Tar, new WriterOptions(CompressionType.GZip), cancellationToken);
await writer.WriteAllAsync(
@"D:\files",
"*",
SearchOption.AllDirectories,
cancellationToken
);Write with progress tracking and cancellation:
var cts = new CancellationTokenSource();
// Set timeout or cancel from UI
cts.CancelAfter(TimeSpan.FromMinutes(5));
using Stream stream = File.OpenWrite("archive.zip");
await using var writer = await WriterFactory.OpenAsyncWriter(stream, ArchiveType.Zip, new WriterOptions(CompressionType.Deflate), cts.Token);
try
{
await writer.WriteAllAsync(@"D:\data", "*", SearchOption.AllDirectories, cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation was cancelled");
}Extract from archive asynchronously:
await using var archive = await ZipArchive.OpenAsyncArchive("archive.zip", cancellationToken: cancellationToken);
// Simple async extraction - works for all archive types
await archive.WriteToDirectoryAsync(
@"C:\output",
cancellationToken: cancellationToken
);Benefits of Async Operations:
- Non-blocking I/O for better application responsiveness
- Improved scalability for server applications
- Support for cancellation via CancellationToken
- Better resource utilization in async/await contexts
- Compatible with modern .NET async patterns