-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add async ZipFile APIs #1541
Comments
This needs someone to write up a formal API proposal then we can review and if approved, offer up for implementation. Anyone? Docs on the API review process and see Jon's API request for a great example of a strong proposal. The more concrete the proposal is (e.g. has examples of usage, real-world scenarios, fleshed out API), the more discussion it will start and the better the chances of us being able to push the addition through the review process. |
Forked the repo, and working on an api proposal. That probably means this item is done, right? |
Not quite, @abdulbeard. This particular issue is around adding ZipFile async APIs. For example, currently we have |
Gotcha @ianhays . I did a terrible job reading the title 😛 |
SummaryCurently, System.IO.Compression.Zipfile doesn't have async methods for Proposed APInamespace System.IO.Compression {
public partial class Zipfile {
public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName, CancellationToken cancellationToken = default(CancellationToken));
public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName, CompressionLevel compressionLevel, bool includeBaseDirectory, CancellationToken cancellationToken = default(CancellationToken));
public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName, CompressionLevel compressionLevel, bool includeBaseDirectory, Encoding entryNameEncoding, CancellationToken cancellationToken = default(CancellationToken));
public static Task ExtractToDirectoryAsync(string sourceArchiveFileName, string destinationDirectoryName, CancellationToken cancellationToken = default(CancellationToken));
public static Task ExtractToDirectoryAsync(string sourceArchiveFileName, string destinationDirectoryName, Encoding entryNameEncoding, CancellationToken cancellationToken = default(CancellationToken));
public static Task<ZipArchive> OpenAsync(string archiveFileName, ZipArchiveMode mode, CancellationToken cancellationToken = default(CancellationToken));
public static Task<ZipArchive> OpenAsync(string archiveFileName, ZipArchiveMode mode, Encoding entryNameEncoding, CancellationToken cancellationToken = default(CancellationToken));
public static Task<ZipArchive> OpenReadAsync(string archiveFileName, CancellationToken cancellationToken = default(CancellationToken));
}
} Expected Use and BenefitsThese new functions are asynchronous versions of their synchronous counterparts. This allows the .net runtime to allow the thread to do other work while the asynchronous operation completes (as mandated by the TPL and async/await scheduling).
|
Here's a proposal ^. Looking forward to constructive feedback and insight. |
Should using (var archive = await ZipFile.OpenAsync("archive.zip", ZipArchiveMode.Update))
{
archive.CreateEntry("empty.txt");
}
using (var archive = await ZipFile.OpenReadAsync("archive.zip"))
{
foreach (var entry in archive.Entries)
{
Console.WriteLine(entry.FullName);
}
} As far as I can tell from skimming the source code, even though the above code uses the proposed |
Here's a proposal for ZipArchive class:namespace System.IO.Compression {
public class ZipArchive {
public Task<ReadOnlyCollection<ZipArchiveEntry>> GetEntriesAsync();
public Task<ZipArchiveEntry> GetEntryAsync(string entryName, CancellationToken cancellationToken = default(cancellationToken));
}
} |
@abdulbeard How does that help? As far as I can tell, |
@svick you're right. Async versions of CreateEntry don't do much, since their underlying functionality is not blocking. public Task<ReadOnlyCollection<ZipArchiveEntry>> GetEntriesAsync();
public Task<ZipArchiveEntry> GetEntryAsync(string entryName, CancellationToken cancellationToken = default(cancellationToken)); Amended the proposal above. |
@JeremyKuhne could you give feedback on this proposal? Perhaps we can move it forward |
Yeah, there isn't a great approach until dotnet/roslyn#114 is added. But in the meantime, I think there are ways to make it work. The approaches I considered are:
Looking at the two examples above, I think adding |
@svick |
@danmosemsft @JeremyKuhne any suggestions for changes to ^, or shall we move forward? |
Anything I can do to help move this along? |
crickets.......... |
@abdulbeard Sorry, the actual owner here moved on to another project and this got lost in the shuffle. 😦 Thanks for poking on it. @stephentoub can you give some direction/guidance on the disposal questions? @ericstj do you think this proposal provides what you were looking for? Presumably |
@ahsonkhan, @ViktorHofer can one of you take this and drive it forward? |
We have added |
It depends. Is ZipArchive lazily reading from a stream, e.g. when you go to read the next entry, might that perform a read on the stream? If yes, then having it return an IAsyncEnumerable could make sense. If no, then it should probably return a synchronous enumerable. |
Just curious is there any update on this issue? We have a .NET Core project that creates zip files in Azure blobs, and we are forced to use the sync version of ZipArchive apis because the async ones will cause unit tests to hang. |
I have some code that is generating and streaming a zip archive on the fly, which fails if AllowSynchronousIO is false, which is the default from core 3.0.0-preview3 onwards: System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead. |
Like @kccsf I have simillar issue with the Kestrel server configured to disallow synchronous IO. With the current implementation I can not use ZipArchive directly with the Kestrel's response stream - because of synchronous disposing. |
Just hit this today as well. :\ After writing the entries, Dispose() throws the exception. Worked around by allowing sync I/O at that point for now, but would be nice to have some async API that I could call to remove the need for sync I/O. |
Is there any workaround for the "Synchronous operations are disallowed" issue writing to a response stream as it looks like any proposal is still a long way off and can't wait for .NET 6+ (and for platform reasons I'm currently limited to .NET Core 3.1) ? Only options with ZipArchive I can see is to write the zip to a temp file and read that back as a stream asynchronously, or use a memory stream but that's not ideal with potentially large zip files. Tried PushStreamContent but it didn't call the content method, and advice is it's not needed in .NET Core as we have Response.Body stream anyway. |
ASP.NET exposes a flag to control that, e.g. services.Configure<KestrelServerOptions>(options =>
{
options.AllowSynchronousIO = true;
}); |
@tjmoore - as a workaround you can allow synchronous operations at controller's method level: |
@stephentoub @askolniak Is that wise though as synchronous IO was disabled for good reason (dotnet/aspnetcore#7644)? Though it's an option. Currently resorting to a temporary file and not sure if that's better or the risk of hanging the app. |
You asked for a workaround. That's the workaround :-) ASP.NET enforces that by default in the name of scalability. It gives you the option of not enforcing it once you decide you're ok with the tradeoffs. It would likely depend on the size of what you're compressing, scalability needs, etc. as to which workaround is best. |
I wonder whether we can advance this proposal to reviewable. Then there would be time for someone passionate to get it into .NET 6. |
I have moved it to 7.0 from Future, will provide a proposal once we are done with clearing the 6.0 bugs backlog. |
I have found a work around for now ... async Task DoZipFileWorkAsync()
{
// ZipArchive is a Synchronous Block, so yield asynchronously
await Task.Yield();
// Open archive
using ZipArchive zipArchive = new(File.OpenRead(path));
// ... do work
} All work is now done on a |
This is not really a work around, since all work with the ZipArchive is still done synchronously. |
I do agree to some extent, however testing done works as expected in a worker thread, not the |
I wanted to try adding async and ended up with copy-pasting dotnet source code of I even added a progress report. The downside is, I had to use english exception message since the methods to get translated messages from resources are internal.
|
All the ZipFile and ZipArchive async APIs should be implemented together. For that reason, I closed #1560 in favor of this issue. There was a good discussion there we should use for reference. There are some problems with the way ZipArchive is implemented that would make it complex to implement the async methods:
|
This issue is now 8 years old... Are there any plans to target this in the near future? 😕 |
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
The text was updated successfully, but these errors were encountered: