Earn More by Sharing What You Love
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Tim Corey
24m 49s
File compression and extraction are essential tasks for many C# developers, whether you're working on a desktop app, a web server, or simply need to archive data. For years, .NET developers could create, update, and extract zip files synchronously, blocking the UI thread or delaying other operations. As Tim Corey explains in his video "Async Zip in .NET 10 - One Line Create or Extract And More", .NET 10 introduces asynchronous operations for zip files, enabling developers to write efficient code that performs I/O-bound operations without freezing the main application.
Asynchronous programming in C# is primarily based on Task and Task<T> objects, with the Task class serving as the core component for modeling ongoing work and enabling concurrent execution. The Task asynchronous programming (TAP) model provides a layer of abstraction over typical asynchronous coding, making it easier to handle asynchronous operations and exceptions. The System.Threading.Tasks.Task class and related types are used to implement the task-based asynchronous programming model in C#.
In this article, we'll follow Tim's explanation and show how to perform asynchronous operations in C# to create, extract, and selectively zip files, all while using the async/await pattern, Task objects, and proper exception handling.
Tim starts by highlighting the long-standing ability of C# to create and extract zip files. However, with .NET 10, developers can now use asynchronous code to handle these tasks without blocking the calling thread. Using async methods and the await keyword, operations like creating or extracting large zip files can run on background threads, allowing the main UI thread to remain responsive.
The async keyword is just a decorator that tells the C# compiler that the method contains at least one occurrence of the await keyword. The await keyword can only be used inside an async method, and it must be followed by a Task or Task<TResult> object.
As Tim explains, this is particularly important in applications that perform multiple tasks, such as network calls, database queries, or file I/O, where awaiting tasks lets the CPU-bound tasks continue without holding up the calling thread. Blocking async code with .Result or .Wait() turns async code back into synchronous code, which can lead to deadlocks.
Async methods return Task or Task<TResult> objects, and returning tasks enables proper task composition and concurrency. Methods without await are executed synchronously. By returning Task objects from asynchronous methods, you can coordinate multiple ongoing operations and handle exceptions more effectively.
Tim begins by opening Visual Studio 2026 and creating a console application named ZipFilesApp. He notes that even static async Task Main methods can be used to perform asynchronous tasks in the entry point of the program, avoiding blocking the main thread. When adapting existing code, you can often wrap synchronous code in Tasks or implement async variants to introduce asynchronous behavior without rewriting the entire codebase.
He then adds the essential using directive:
using System.IO.Compression;
This enables access to asynchronous methods such as CreateFromDirectoryAsync and ExtractToDirectoryAsync. When creating calling methods, it's important to use consistent naming conventions, such as appending the 'Async' suffix to asynchronous methods, to clearly distinguish them from synchronous ones. Inconsistent method naming can lead to confusion about whether a method is synchronous or asynchronous.
Next, Tim prepares three string variables to define the paths needed for zip operations:
Tim mentions using the @ symbol in string literals to avoid escaping backslashes, which is a common issue in synchronous code when writing Windows paths. Throughout this article, code samples and code snippets are provided to illustrate how to implement asynchronous programming in C#.
At 3:47, Tim demonstrates a one-line async operation using a static async Task method:
await ZipFile.CreateFromDirectoryAsync(
sourceDirectory,
destinationZipFile,
CompressionLevel.SmallestSize,
includeBaseDirectory: false
);
Here, Tim explains several important aspects:
This example shows how the asynchronous workflow in C# is managed by the async and await keywords, making code more maintainable and simplifying exception handling. The await keyword can only be used inside an async method, such as a public async Task method. When await is encountered, the C# compiler generates a state machine to manage the execution flow, pausing execution, starting the asynchronous task, and resuming once it completes. If the async method returns a value, the result can be captured in a variable, such as int result, for later use. By using async/await, developers don't have to manually create background threads or thread pool tasks, as the compiler generates the necessary state machine and supporting code automatically.
Tim moves on to extracting a zip file with asynchronous operations, using another async method:
await ZipFile.ExtractToDirectoryAsync(
destinationZipFile,
destinationDirectory,
overwriteFiles: false
);
He points out that the overwriteFiles parameter handles exception handling in case the folder already exists. By default, extraction will fail if files already exist, but setting it to true allows the async operation to replace existing files.
When working with asynchronous code, robust error handling is crucial. Tim emphasizes the importance of using try-catch blocks to handle exceptions in async methods. Not handling exceptions properly in async code can lead to silent failures or unexpected crashes, as exceptions thrown in asynchronous tasks may not be immediately visible. Always handle exceptions to ensure reliable application behavior.
Tim notes that this async code is safe for multiple tasks, meaning it won't block other requests or network calls happening simultaneously. Using the await keyword, the calling thread is released until the task finishes, creating a responsive program flow.
Additionally, when defining async methods, it's important to use async void only for event handlers, such as button clicks in GUI applications. In these cases, the method signature typically includes (object sender, EventArgs e), where object sender identifies the source of the event. Using async void outside of event handlers can lead to unpredictable behavior and hard-to-diagnose bugs, so prefer async Task for most asynchronous methods.
Sometimes, developers need more control than simply zipping an entire folder. Tim demonstrates performing asynchronous operations selectively by creating a FileStream:
await using FileStream zipStream = new FileStream(
destinationZipFile,
FileMode.Create,
FileAccess.Write,
FileShare.None,
bufferSize: 4096,
useAsync: true
);
When adding files asynchronously, each file addition represents a specific task. By starting all the tasks for file additions concurrently and then awaiting their completion using methods like Task.WhenAll, you can significantly improve performance. Managing running tasks in this way is essential for high-concurrency scenarios, such as when a server needs to process multiple requests at once. This approach is also commonly used when interacting with a remote server, allowing applications to remain responsive while waiting for data.
Tim explains each detail:
He notes that async programming in this context avoids blocking the main thread while the task completes, which is especially useful on web servers handling multiple tasks. High-concurrency servers leverage asynchronous programming to efficiently handle multiple simultaneous client requests without spawning a new thread for each connection. However, writing async code sequentially can make it harder to read and debug, as async tasks may complete out of order.
Next, Tim creates a ZipArchive asynchronously:
using ZipArchive archive = await ZipArchive.CreateAsync(
zipStream,
ZipArchiveMode.Create,
leaveOpen: false,
entryNameEncoding: null
);
foreach (string filePath in Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories))
{
string relativePath = Path.GetRelativePath(sourceDirectory, filePath);
await archive.CreateEntryFromFileAsync(filePath, relativePath).ConfigureAwait(false);
}
This demonstrates async code in practice, where each file addition is an asynchronous call, and the await keyword ensures proper sequencing while still performing non-blocking I/O. Asynchronous code allows the current thread to remain unblocked, improving responsiveness and allowing the thread to perform other work while waiting for I/O operations to complete.
Tim highlights a common exception handling scenario: attempting to extract a zip before the original FileStream task completes. Using file-scoped async void methods or async Task methods without proper scoped using statements can cause:
The process cannot access the file because it is being used by another process.
The solution is to wrap the async operation in a traditional scoped using block to release resources before extraction:
await using (FileStream zipStream = new FileStream(...))
{
using ZipArchive archive = await ZipArchive.CreateAsync(zipStream, ...);
// Add files asynchronously
}
// Now it's safe to extract
This ensures the task object completes and releases the synchronization context, avoiding file locking errors.
Tim concludes that asynchronous zip operations in .NET 10 allow developers to perform asynchronous operations efficiently, avoid blocking the main thread, and integrate seamlessly with other tasks like network calls or database queries.
Using static async Task methods, async modifiers, and await expressions, developers can:
Tim's examples demonstrate that async/await in file operations isn't just convenient - it's an essential part of modern asynchronous programming in C#.
By following his video, developers can write efficient code using async methods, await calls, and asynchronous workflows to handle both simple and complex file compression tasks seamlessly.
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!
Join our newsletter, you’ll get exclusive access on article updates. We value your privacy