Created
October 11, 2018 07:51
-
-
Save ignas-sakalauskas/54e3a4fe46a364263016464fe90a7cd7 to your computer and use it in GitHub Desktop.
ZipArchive Async Playground
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.IO.Compression; | |
using System.Linq; | |
using System.Threading.Tasks; | |
namespace ZipAsync | |
{ | |
public class Program | |
{ | |
public static async Task Main(string[] args) | |
{ | |
var filesToZip = new List<string> { "file1.txt", "file2.txt", "file3.txt" }; | |
// Current our approach | |
// All sync, works well, since in sync approach all delegates are executed one after another | |
// ReSharper warning is ReSharper's bug as Stefano found. | |
var allSyncResult = AllSync(filesToZip); | |
await File.WriteAllBytesAsync("AllSync.zip", allSyncResult); | |
// Previous our approach after my code review | |
// Crashes with exception: Cannot access a disposed object. Object name: 'System.IO.Compression.ZipArchive'. | |
// Meaning -- async delegate (Func<Task>) starts right away after the task object is created (returns hot), | |
// however we are not waiting for these tasks to complete, and proceed with the disposal. | |
// Therefore, some tasks, will access the archive after it's been expired. | |
// See Console.WriteLine message order. | |
// Also, nice comment by Stephen Cleary https://stackoverflow.com/a/18667708/5540176, we effectively have anonymoues `Async Void` return types | |
var syncAsyncMixResult = SyncAsyncMix(filesToZip); | |
await File.WriteAllBytesAsync("SyncAsyncMix.zip", syncAsyncMixResult); | |
// Async all the way -- my preferred approach | |
// Useful when IO/Network operations are involved, e.g. download file, read file from file system etc. | |
var asyncAllTheWayResult = await AsyncAllTheWay(filesToZip); | |
await File.WriteAllBytesAsync("AsyncAllTheWay.zip", asyncAllTheWayResult); | |
// Crashes with exception: System.IO.IOException: Entries cannot be created while previously created entries are still open. | |
// Meaning -- parallel compression not supported :) | |
var asyncParallelResult = await AsyncParallel(filesToZip); | |
await File.WriteAllBytesAsync("AsyncParallel.zip", asyncParallelResult); | |
Console.WriteLine("Press any key to exit..."); | |
Console.ReadKey(); | |
} | |
private static async Task<byte[]> AsyncParallel(IEnumerable<string> filesToZip) | |
{ | |
Console.WriteLine(nameof(AsyncParallel)); | |
using (var ms = new MemoryStream()) | |
{ | |
using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true)) | |
{ | |
var tasks = filesToZip.Select(fileName => CompressSingleFile(archive, fileName)).ToList(); | |
try | |
{ | |
await Task.WhenAll(tasks); | |
} | |
catch (Exception ex) | |
{ | |
Console.WriteLine(ex.Message); | |
} | |
} | |
return ms.ToArray(); | |
} | |
} | |
// private helper for parallel compression | |
private static async Task CompressSingleFile(ZipArchive archive, string fileName) | |
{ | |
var fileData = await File.ReadAllBytesAsync(fileName); | |
var zipArchiveEntry = archive.CreateEntry(fileName, CompressionLevel.Fastest); | |
using (var zipStream = zipArchiveEntry.Open()) | |
await zipStream.WriteAsync(fileData, 0, fileData.Length); | |
} | |
private static async Task<byte[]> AsyncAllTheWay(IEnumerable<string> filesToZip) | |
{ | |
Console.WriteLine(nameof(AsyncAllTheWay)); | |
using (var ms = new MemoryStream()) | |
{ | |
using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true)) | |
{ | |
foreach (var fileName in filesToZip) | |
{ | |
var fileData = await File.ReadAllBytesAsync(fileName); | |
var zipArchiveEntry = archive.CreateEntry(fileName, CompressionLevel.Fastest); | |
using (var zipStream = zipArchiveEntry.Open()) | |
await zipStream.WriteAsync(fileData, 0, fileData.Length); | |
} | |
} | |
return ms.ToArray(); | |
} | |
} | |
private static byte[] SyncAsyncMix(List<string> filesToZip) | |
{ | |
Console.WriteLine(nameof(SyncAsyncMix)); | |
using (var ms = new MemoryStream()) | |
{ | |
Console.WriteLine("0"); | |
using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true)) | |
{ | |
Console.WriteLine("1"); | |
filesToZip.ForEach(async fileName => | |
{ | |
try | |
{ | |
Console.WriteLine($"2 - {fileName}"); | |
var fileData = await File.ReadAllBytesAsync(fileName); | |
var zipArchiveEntry = archive.CreateEntry(fileName, CompressionLevel.Fastest); | |
using (var zipStream = zipArchiveEntry.Open()) | |
zipStream.Write(fileData, 0, fileData.Length); | |
Console.WriteLine($"3 - {fileName}"); | |
} | |
catch (Exception ex) | |
{ | |
Console.WriteLine(ex.Message); | |
} | |
Console.WriteLine($"4 - {fileName}"); | |
}); | |
Console.WriteLine("5"); | |
} | |
Console.WriteLine("6"); | |
return ms.ToArray(); | |
} | |
} | |
private static byte[] AllSync(List<string> filesToZip) | |
{ | |
Console.WriteLine(nameof(AllSync)); | |
using (var ms = new MemoryStream()) | |
{ | |
Console.WriteLine("0"); | |
using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true)) | |
{ | |
Console.WriteLine("1"); | |
filesToZip.ForEach(fileName => | |
{ | |
try | |
{ | |
Console.WriteLine($"2 - {fileName}"); | |
var fileData = File.ReadAllBytes(fileName); | |
var zipArchiveEntry = archive.CreateEntry(fileName, CompressionLevel.Fastest); | |
using (var zipStream = zipArchiveEntry.Open()) | |
zipStream.Write(fileData, 0, fileData.Length); | |
Console.WriteLine($"3 - {fileName}"); | |
} | |
catch (Exception ex) | |
{ | |
Console.WriteLine(ex.Message); | |
} | |
Console.WriteLine($"4 - {fileName}"); | |
}); | |
Console.WriteLine("5"); | |
} | |
Console.WriteLine("6"); | |
return ms.ToArray(); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment