Skip to footer content
Iron Academy Logo
What's New in C# and .NET

Async C#: A Deep Dive into Async Zip with Tim Corey

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 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.

Introduction to Asynchronous Programming and Async Zip in .NET 10

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 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 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.

Setting Up the Project

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:

  1. Source directory – The folder to compress.

  2. Destination zip file – The full path to save the zip file.

  3. Destination directory – The folder where extracted files will be stored.

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#.

Creating a Zip File Using an Async Method

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:

  • The async keyword marks this asynchronous method, enabling the use of await.

  • await pauses execution of this async method until the task completes, allowing other tasks or the main UI thread to continue running. This demonstrates how await work and pause execution together to ensure non-blocking behavior in asynchronous operations such as file I/O.

  • The CompressionLevel.SmallestSize ensures efficient compression.

  • The includeBaseDirectory parameter controls whether the root folder is included in the zip.

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.

Extracting a Zip File with Await Task Asynchronously

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.

Selectively Adding Files to a Zip

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:

  • FileMode.Create – Ensures a new zip file is created or overwritten.

  • FileAccess.Write – Allows writing to the file.

  • FileShare.None – Prevents other tasks from accessing the file while it’s being written.

  • useAsync: true – Enables asynchronous file writing, a core async operation.

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.

Creating the Zip Archive and Adding Files

Next, Tim creates a ZipArchive asynchronously:

using ZipArchive archive = await ZipArchive.CreateAsync(
    zipStream, 
    ZipArchiveMode.Create, 
    leaveOpen: false, 
    entryNameEncoding: null
);
  • The await expression ensures that execution pauses until the task finishes, making it safe to proceed with adding files. Async and await simplify the management of asynchronous operations, making the code easier to read and maintain compared to traditional callback-based approaches.

  • It's important to note that the same code can behave differently depending on the synchronization context. For example, in WPF or WinForms applications, the presence of a synchronization context can cause the continuation after an await call to run on the main UI thread, while in console applications, there is no such context. This can affect how your async code executes.

  • When writing library code, consider using ConfigureAwait(false) after an await call to avoid unnecessary context switching and improve performance.

  • Using Path.GetRelativePath, Tim explains how to maintain folder structure inside the zip, which is essential when performing asynchronous workflows with multiple files:
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.

Handling File Access and Exception Handling

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.

Conclusion: Efficient Async Zip in .NET 10

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:

  • Compress or extract zip files without freezing the UI thread.

  • Handle multiple threads safely without manual thread pool management.

  • Perform selective zipping while preserving folder structures.

  • Ensure proper exception handling and resource cleanup.

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.

Hero Worlddot related to Async C#: A Deep Dive into Async Zip with Tim Corey
Hero Affiliate related to Async C#: A Deep Dive into Async Zip with Tim Corey

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!

Iron Support Team

We're online 24 hours, 5 days a week.
Chat
Email
Call Me