Skip to content

Instantly share code, notes, and snippets.

@ignas-sakalauskas
Created October 11, 2018 07:51
Show Gist options
  • Save ignas-sakalauskas/54e3a4fe46a364263016464fe90a7cd7 to your computer and use it in GitHub Desktop.
Save ignas-sakalauskas/54e3a4fe46a364263016464fe90a7cd7 to your computer and use it in GitHub Desktop.
ZipArchive Async Playground
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