Open
Description
All ZipFile APIs are currently synchronous. This means manipulations to zip files will always block a thread. We should investigate using an async file and calling async IO APIs (ReadAsync/WriteAsync) to free up the thread while IO is happening. dotnet/corefx#5680
Edit by @carlossanlop:
System.IO.Compression
- public partial class ZipArchive : IDisposable
+ public partial class ZipArchive : IAsyncDisposable, IDisposable
{
public void Dispose();
protected virtual void Dispose(bool disposing);
+ public ValueTask DisposeAsync(); // From the new implemented interface
public ReadOnlyCollection<ZipArchiveEntry> Entries { get; }
+ public Task<ReadOnlyCollection<ZipArchiveEntry>> GetEntriesAsync(CancellationToken cancellationToken = default);
+ // Should this use IAsyncEnumerable?
public ZipArchiveEntry? GetEntry(string entryName);
+ public Task<ZipArchiveEntry?> GetEntryAsync(string entryName, CancellationToken cancellationToken = default);
...
Note there is no CreateEntryAsync
above. That is because I didn't find any code inside CreateEntry
(or inside DoCreateEntry
) that can be made async, Aside for the cancellationToken.ThrowIfCancellationRequested()
call that would have to be added at the top.
But here's the method in case we want to add it regardless. We can get it approved here, and if in the end it is not needed, we don't have to ship it:
...
public ZipArchiveEntry CreateEntry(string entryName);
+ public Task<ZipArchiveEntry> CreateEntryAsync(string entryName, CancellationToken cancellationToken = default);
}
Also:
public partial class ZipArchiveEntry
{
void Delete();
+ public Task DeleteAsync(CancellationToken cancellationToken = default);
public Stream Open();
+ Task<Stream> OpenAsync(CancellationToken cancellationToken = default);
}
Usage examples:
// Open read, get entries, get entry stream
FileStream fs = File.OpenRead("/path/to/archive.zip");
await using (ZipArchive archive = new ZipArchive(stream: fs, ZipArchiveMode.Read, ct))
{
foreach (ZipArchiveEntry entry in await archive.GetEntriesAsync(ct))
{
Stream s = await entry.OpenAsync(ct);
// Do something
}
}
// Open update, get entries, delete entry
FileStream fs = File.OpenRead("/path/to/archive.zip");
await using (ZipArchive archive = new ZipArchive(stream: fs, ZipArchiveMode.Update, ct))
{
foreach (ZipArchiveEntry entry in await archive.GetEntriesAsync(ct))
{
if (/* some condition */)
{
await entry.DeleteAsync(ct);
}
}
}
System.IO.Compression.ZipFile
public static partial class ZipFile
{
public static void CreateFromDirectory(string sourceDirectoryName, Stream destination);
+ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream destination, CancellationToken cancellationToken = default);
public static void CreateFromDirectory(string sourceDirectoryName, Stream destination, CompressionLevel compressionLevel, bool includeBaseDirectory);
+ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream destination, CompressionLevel compressionLevel, bool includeBaseDirectory, CancellationToken cancellationToken = default);
public static void CreateFromDirectory(string sourceDirectoryName, Stream destination, CompressionLevel compressionLevel, bool includeBaseDirectory, Encoding? entryNameEncoding);
+ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream destination, CompressionLevel compressionLevel, bool includeBaseDirectory, Encoding? entryNameEncoding, CancellationToken cancellationToken = default);
public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName);
+ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName, CancellationToken cancellationToken = default);
public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, CompressionLevel compressionLevel, bool includeBaseDirectory);
+ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName, CompressionLevel compressionLevel, bool includeBaseDirectory, CancellationToken cancellationToken = default);
public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, CompressionLevel compressionLevel, bool includeBaseDirectory, Encoding? entryNameEncoding);
+ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName, CompressionLevel compressionLevel, bool includeBaseDirectory, Encoding? entryNameEncoding, CancellationToken cancellationToken = default);
public static void ExtractToDirectory(Stream source, string destinationDirectoryName);
+ public static Task ExtractToDirectoryAsync(Stream source, string destinationDirectoryName, CancellationToken cancellationToken = default);
public static void ExtractToDirectory(Stream source, string destinationDirectoryName, bool overwriteFiles);
+ public static Task ExtractToDirectoryAsync(Stream source, string destinationDirectoryName, bool overwriteFiles, CancellationToken cancellationToken = default);
public static void ExtractToDirectory(Stream source, string destinationDirectoryName, Encoding? entryNameEncoding);
+ public static Task ExtractToDirectoryAsync(Stream source, string destinationDirectoryName, Encoding? entryNameEncoding, CancellationToken cancellationToken = default);
public static void ExtractToDirectory(Stream source, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles);
+ public static Task ExtractToDirectoryAsync(Stream source, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles, CancellationToken cancellationToken = default);
public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName);
+ public static Task ExtractToDirectoryAsync(string sourceArchiveFileName, string destinationDirectoryName, CancellationToken cancellationToken = default);
public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, bool overwriteFiles);
+ public static Task ExtractToDirectoryAsync(string sourceArchiveFileName, string destinationDirectoryName, bool overwriteFiles, CancellationToken cancellationToken = default);
public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, Encoding? entryNameEncoding);
+ public static Task ExtractToDirectoryAsync(string sourceArchiveFileName, string destinationDirectoryName, Encoding? entryNameEncoding, CancellationToken cancellationToken = default);
public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles);
+ public static Task ExtractToDirectoryAsync(string sourceArchiveFileName, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles, CancellationToken cancellationToken = default);
public static ZipArchive Open(string archiveFileName, ZipArchiveMode mode);
+ public static Task<ZipArchive> OpenAsync(string archiveFileName, ZipArchiveMode mode, CancellationToken cancellationToken = default);
public static ZipArchive Open(string archiveFileName, ZipArchiveMode mode, Encoding? entryNameEncoding);
+ public static Task<ZipArchive> OpenAsync(string archiveFileName, ZipArchiveMode mode, Encoding? entryNameEncoding, CancellationToken cancellationToken = default);
public static ZipArchive OpenRead(string archiveFileName);
+ public static Task<ZipArchive> OpenReadAsync(string archiveFileName, CancellationToken cancellationToken = default);
}
public static partial class ZipFileExtensions
{
public static ZipArchiveEntry CreateEntryFromFile(this ZipArchive destination, string sourceFileName, string entryName);
+ public static Task<ZipArchiveEntry> CreateEntryFromFileAsync(this ZipArchive destination, string sourceFileName, string entryName, CancellationToken cancellationToken = default);
public static ZipArchiveEntry CreateEntryFromFile(this ZipArchive destination, string sourceFileName, string entryName, CompressionLevel compressionLevel);
+ public static Task<ZipArchiveEntry> CreateEntryFromFileAsync(this ZipArchive destination, string sourceFileName, string entryName, CompressionLevel compressionLevel, CancellationToken cancellationToken = default);
public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName);
+ public static Task ExtractToDirectoryAsync(this ZipArchive source, string destinationDirectoryName, CancellationToken cancellationToken = default);
public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, bool overwriteFiles);
+ public static Task ExtractToDirectoryAsync(this ZipArchive source, string destinationDirectoryName, bool overwriteFiles, CancellationToken cancellationToken = default);
public static void ExtractToFile(this ZipArchiveEntry source, string destinationFileName);
+ public static Task ExtractToFileAsync(this ZipArchiveEntry source, string destinationFileName, CancellationToken cancellationToken = default);
public static void ExtractToFile(this ZipArchiveEntry source, string destinationFileName, bool overwrite);
+ public static Task ExtractToFileAsync(this ZipArchiveEntry source, string destinationFileName, bool overwrite, CancellationToken cancellationToken = default);
}
Usage examples
/// ZipFile creation and extraction
MemoryStream ms = new();
await CreateFromDirectoryAsync(sourceDirectoryName: "C:/path/to/source/folder", destination: ms, ct);
ms.Position = 0;
await ExtractToDirectoryAsync(source: ms, destinationDirectoryName: "C:/path/to/destination/folder", ct);
/// ZipFile open, ZipFileExtensions create entry from file
await using (ZipArchive archive = await ZipFile.OpenAsync(archiveFileName: "/path/to/archive.zip", ZipArchiveMode.Update, ct))
{
await archive.CreateEntryFromFileAsync(sourceFileName: "/path/to/file.txt", entryName: "file.txt", ct);
}
// ZipFile open read, ZipFileExtensions extract entry to file
await using (ZipArchive archive = await ZipFile.OpenReadAsync(archiveFileName: "/path/to/archive.zip", ct))
{
foreach (ZipArchiveEntry entry in await archive.GetEntriesAsync(ct))
{
string fileName = Path.Combine(rootFolder, entry.Name);
await archive.ExtractToFileAsync(destinationFileName: fileName, overwrite: true, ct);
}
// or instead
await archive.ExtractToDirectoryAsync(rootFolder, ct);
}
Plan
- Get the APIs approved for System.IO.Compression and System.IO.Compression.ZipFile.
- 1st round: Implementation of System.IO.Compression APIs:
- Change the block structs to classes. Change ZipBlocks structs to classes #113453
- Add zip benchmarks Add benchmarks for
ZipFile.CreateFromDirectory
andZipFile.ExtractToDirectory
performance#4764 - Create the async version of the internal APIs, reuse as much code as needed from the sync version.
- Implement the public async APIs.
- Add unit tests.
- Add XML comments to the public APIs.
- Find locations in the dotnet repos where the async APIs of System.IO.Compression could be used and change them.
- 2nd round: Implementation of System.IO.Compression.ZipFile APIs:
- Implement the public async APIs.
- Add unit tests.
- Add XML comments to the public APIs.
- Find locations in the dotnet repos where the async APIs of System.IO.Compression could be used and change them.
- 3rd round: Create a proposal for System.IO.Packaging async APIs (Future).