# How to Use Async and Multithreading for QR Code Operations in C#
Single-threaded QR scanning blocks the calling thread for the duration of every image decode.
In a WPF button handler, this freezes the UI until decoding completes. In a batch job processing hundreds of images, it leaves CPU cores idle when they could be working in parallel. IronQR's `ReadAsync` method offloads individual reads to an awaitable task, and the standard `Read` method works with `Parallel.ForEach` and `Task.WhenAll` for batch throughput.
This guide demonstrates how to process QR codes asynchronously, distribute batch reads across CPU cores, and combine both patterns for high-volume pipelines.
*as-heading:2(Quickstart: Process QR Codes Asynchronously)*
Load an image and await the decoded results without blocking the calling thread.
```csharp
:title=Quickstart
using IronQr;
using IronSoftware.Drawing;
var input = new QrImageInput(AnyBitmap.FromFile("ticket.png"));
IEnumerable<QrResult> results = await new QrReader().ReadAsync(input);
Console.WriteLine(results.First().Value);
```
<div class="hsg-featured-snippet">
<h3>Minimal Workflow (5 steps)</h3>
<ol>
<li><a class="js-modal-open" data-modal-id="trial-license-after-download" href="https://nuget.org/packages/IronQR/">Download the IronQR C# library for async QR code processing</a></li>
<li>Use <code>ReadAsync</code> for non-blocking single reads</li>
<li>Use <code>Parallel.ForEach</code> for CPU-bound batch processing</li>
<li>Combine with <code>SemaphoreSlim</code> for bounded concurrency pipelines</li>
<li>Collect results from <code>IEnumerable<QrResult></code> and print the decoded values</li>
</ol>
</div>
## Reading QR Codes Asynchronously
`ReadAsync` returns an awaitable task, making it compatible with WPF/MAUI event handlers, ASP.NET controller actions, or any async method. The input must be constructed from an image bitmap; there is no file-path overload.
The write side is synchronous and has no async variants. To avoid blocking the thread during file I/O, wrap the save step in `File.WriteAllBytesAsync()` using the raw bytes exported from the bitmap.
### Input
A QR code event badge scanned and regenerated to demonstrate the async read-and-write pattern.
<div class="content-img-align-center">
<div class="center-image-wrapper" style="max-width: 300px;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-async-event-badge.webp"
alt="QR code encoding https://ironsoftware.com/event-badge used as async read input"
class="img-responsive add-shadow" />
</div>
</div>
```cs
:path=/static-assets/qr/content-code-examples/how-to/async-and-multithreading/async-read-write.cs
```
### Output
The terminal displays the decoded QR type and value in the format `[QrType] Value`, then confirms that `output-qr.png` was saved.
<div class="content-img-align-center">
<div class="center-image-wrapper">
<img src="/static-assets/qr/how-to/async-and-multithreading/test1-async-out.webp" alt="Terminal output showing [QRCode] decoded value and output-qr.png save confirmation" class="img-responsive add-shadow" />
</div>
</div>
`QrScanMode.Auto` runs both ML detection and a basic scan pass, populating the decoded value and QR type in each result. `OnlyDetectionModel` is faster but returns bounding box coordinates only, leaving the value field empty. Use `Auto` whenever the encoded content is needed.
---
## Processing QR Codes with Multithreading
For images that can be decoded independently, `Parallel.ForEach` distributes work across available CPU cores. A separate `QrReader` instance per iteration is the safe default, as IronQR makes no explicit thread-safety guarantee for shared reader instances.
### Input
Four of the ten QR code test images used in the parallel batch scan. Each image encodes a URL and is read from the `qr-images/` folder at runtime.
<div class="competitors-section__wrapper-even-1">
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-batch-qr-1.webp"
alt="Batch QR code input image 1 of 10 encoding https://ironsoftware.com/batch-1"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Image 1 (Batch 1 of 10)
</p>
</div>
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-batch-qr-2.webp"
alt="Batch QR code input image 2 of 10 encoding https://ironsoftware.com/batch-2"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Image 2 (Batch 2 of 10)
</p>
</div>
</div>
<div class="competitors-section__wrapper-even-1">
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-batch-qr-3.webp"
alt="Batch QR code input image 3 of 10 encoding https://ironsoftware.com/batch-3"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Image 3 (Batch 3 of 10)
</p>
</div>
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-batch-qr-4.webp"
alt="Batch QR code input image 4 of 10 encoding https://ironsoftware.com/batch-4"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Image 4 (Batch 4 of 10)
</p>
</div>
</div>
```cs
:path=/static-assets/qr/content-code-examples/how-to/async-and-multithreading/parallel-batch.cs
```
### Output
The console displays a batch summary, including the number of files processed, processing time, QR codes found, any failures, and throughput. It then lists each filename with its decoded URL.
<div class="content-img-align-center">
<div class="center-image-wrapper">
<img src="/static-assets/qr/how-to/async-and-multithreading/test2-async-output.webp" alt="Terminal output showing parallel batch results: 10 files processed, QR codes found, failures, throughput, and decoded URL per filename" class="img-responsive add-shadow" />
</div>
</div>
[Download all 10 test batch QR code input images (batch-qr-images.zip).](/static-assets/qr/how-to/async-and-multithreading/batch-qr-images.zip)
`ConcurrentBag<T>` gathers results from all threads without requiring locks. A thread-safe counter tracks failures, and using try-catch for each file ensures that one bad image does not interrupt the entire batch. This approach follows the error-isolation pattern described in the [error handling how-to](https://ironsoftware.com/csharp/qr/how-to/detailed-error-messages/).
Set `MaxDegreeOfParallelism` to `Environment.ProcessorCount` to align with the number of CPU cores. Using additional threads increases overhead and does not improve performance, particularly for CPU-intensive ML models.
---
## Combining Async and Parallel Processing
For high-volume pipelines, pair `SemaphoreSlim` with `Task.WhenAll` to bound concurrency. Unlike `Parallel.ForEach`, this pattern keeps I/O non-blocking while controlling how many decodes run at once, preventing thread pool saturation under large workloads.
### Input
Four of the twenty QR code test images processed by the concurrent pipeline. Each image encodes a URL and is decoded in parallel using bounded concurrency via `SemaphoreSlim`.
<div class="competitors-section__wrapper-even-1">
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-pipeline-qr-1.webp"
alt="Pipeline QR code input image 1 of 20"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Image 1 (Pipeline 1 of 20)
</p>
</div>
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-pipeline-qr-2.webp"
alt="Pipeline QR code input image 2 of 20"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Image 2 (Pipeline 2 of 20)
</p>
</div>
</div>
<div class="competitors-section__wrapper-even-1">
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-pipeline-qr-3.webp"
alt="Pipeline QR code input image 3 of 20"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Image 3 (Pipeline 3 of 20)
</p>
</div>
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-pipeline-qr-4.webp"
alt="Pipeline QR code input image 4 of 20"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Image 4 (Pipeline 4 of 20)
</p>
</div>
</div>
```cs
:path=/static-assets/qr/content-code-examples/how-to/async-and-multithreading/semaphore-pipeline.cs
```
### Output
The console displays a summary when the pipeline finishes: total QR codes decoded, number of source files, and elapsed time, followed by each filename and its decoded URL.
<div class="content-img-align-center">
<div class="center-image-wrapper">
<img src="/static-assets/qr/how-to/async-and-multithreading/test3async-output.webp" alt="Terminal output showing pipeline results: 20 QR codes from 20 files with decoded URL per filename" class="img-responsive add-shadow" />
</div>
</div>
[Download all 20 test pipeline QR code input images (high-volume-qr-images.zip).](/static-assets/qr/how-to/async-and-multithreading/high-volume-qr-images.zip)
Match the semaphore limit to the available core count for throughput, or lower it when memory pressure is a concern with large images.
---
## Further Reading
- [ML Scanning Example](https://ironsoftware.com/csharp/qr/examples/read-qr-with-machine-learning/): scan mode comparison with code samples.
- [Reading QR Codes How-To](https://ironsoftware.com/csharp/qr/how-to/read-qr-codes-from-image/): input construction and basic read patterns.
- [QR Code Generator Tutorial](https://ironsoftware.com/csharp/qr/tutorials/csharp-qr-code-generator/): generation with styling.
- [QrReader API Reference](https://ironsoftware.com/csharp/qr/object-reference/api/IronQr.QrReader.html): method signatures and remarks.
- [QrWriter API Reference](https://ironsoftware.com/csharp/qr/object-reference/api/IronQr.QrWriter.html): all write overloads.
- [Error Handling How-To](https://ironsoftware.com/csharp/qr/how-to/detailed-error-messages/): per-file error isolation and logging patterns.
[View licensing options](https://ironsoftware.com/csharp/qr/licensing/) when the pipeline is ready for production.
Single-threaded QR scanning blocks the calling thread for the duration of every image decode.
In a WPF button handler, this freezes the UI until decoding completes. In a batch job processing hundreds of images, it leaves CPU cores idle when they could be working in parallel. IronQR's ReadAsync method offloads individual reads to an awaitable task, and the standard Read method works with Parallel.ForEach and Task.WhenAll for batch throughput.
This guide demonstrates how to process QR codes asynchronously, distribute batch reads across CPU cores, and combine both patterns for high-volume pipelines.
Quickstart: Process QR Codes Asynchronously
Load an image and await the decoded results without blocking the calling thread.
1Install IronQR with NuGet Package Manager
PM > Install-Package IronQR
Install-Package IronQR
2Copy and run this code snippet.
using IronQr;using IronSoftware.Drawing;var input = new QrImageInput(AnyBitmap.FromFile("ticket.png"));IEnumerable<QrResult> results = await new QrReader().ReadAsync(input);Console.WriteLine(results.First().Value);
using IronQr;
using IronSoftware.Drawing;
var input = new QrImageInput(AnyBitmap.FromFile("ticket.png"));
IEnumerable<QrResult> results = await new QrReader().ReadAsync(input);
Console.WriteLine(results.First().Value);
C#
3Deploy to test on your live environment
Start using IronQR in your project today with a free trial
Use Parallel.ForEach for CPU-bound batch processing
Combine with SemaphoreSlim for bounded concurrency pipelines
Collect results from IEnumerable<QrResult> and print the decoded values
Reading QR Codes Asynchronously
ReadAsync returns an awaitable task, making it compatible with WPF/MAUI event handlers, ASP.NET controller actions, or any async method. The input must be constructed from an image bitmap; there is no file-path overload.
The write side is synchronous and has no async variants. To avoid blocking the thread during file I/O, wrap the save step in File.WriteAllBytesAsync() using the raw bytes exported from the bitmap.
Input
A QR code event badge scanned and regenerated to demonstrate the async read-and-write pattern.
using IronQr;using IronQr.Enum;using IronSoftware.Drawing;// --- Async read: non-blocking QR decode ---var inputBmp = AnyBitmap.FromFile("event-badge.png");var imageInput = new QrImageInput(inputBmp, QrScanMode.Auto);var reader = new QrReader();IEnumerable<QrResult> results = await reader.ReadAsync(imageInput);foreach (QrResult result in results){Console.WriteLine($"[{result.QrType}] {result.Value}");}// --- Async-wrapped save: QrWriter.Write() and QrCode.Save() are synchronous ---QrCode qrCode = QrWriter.Write("https://ironsoftware.com");AnyBitmap qrImage = qrCode.Save();// Save the bitmap bytes asynchronously (not an IronQR API — standard .NET async I/O)byte[] pngBytes = qrImage.ExportBytes();awaitFile.WriteAllBytesAsync("output-qr.png", pngBytes);
using IronQr;
using IronQr.Enum;
using IronSoftware.Drawing;
// --- Async read: non-blocking QR decode ---
var inputBmp = AnyBitmap.FromFile("event-badge.png");
var imageInput = new QrImageInput(inputBmp, QrScanMode.Auto);
var reader = new QrReader();
IEnumerable<QrResult> results = await reader.ReadAsync(imageInput);
foreach (QrResult result in results)
{
Console.WriteLine($"[{result.QrType}] {result.Value}");
}
// --- Async-wrapped save: QrWriter.Write() and QrCode.Save() are synchronous ---
QrCode qrCode = QrWriter.Write("https://ironsoftware.com");
AnyBitmap qrImage = qrCode.Save();
// Save the bitmap bytes asynchronously (not an IronQR API — standard .NET async I/O)
byte[] pngBytes = qrImage.ExportBytes();
await File.WriteAllBytesAsync("output-qr.png", pngBytes);
C#
Output
The terminal displays the decoded QR type and value in the format [QrType] Value, then confirms that output-qr.png was saved.
QrScanMode.Auto runs both ML detection and a basic scan pass, populating the decoded value and QR type in each result. OnlyDetectionModel is faster but returns bounding box coordinates only, leaving the value field empty. Use Auto whenever the encoded content is needed.
Processing QR Codes with Multithreading
For images that can be decoded independently, Parallel.ForEach distributes work across available CPU cores. A separate QrReader instance per iteration is the safe default, as IronQR makes no explicit thread-safety guarantee for shared reader instances.
Input
Four of the ten QR code test images used in the parallel batch scan. Each image encodes a URL and is read from the qr-images/ folder at runtime.
Image 1 (Batch 1 of 10)
Image 2 (Batch 2 of 10)
Image 3 (Batch 3 of 10)
Image 4 (Batch 4 of 10)
using IronQr;using IronQr.Enum;using IronSoftware.Drawing;using System.Collections.Concurrent;using System.Diagnostics;string[] files = Directory.GetFiles("qr-images/", "*.png");var allResults = new ConcurrentBag<(stringFile, stringValue)>();int failCount = 0;var sw = Stopwatch.StartNew();Parallel.ForEach(files, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, file =>{ try { var input = new QrImageInput(AnyBitmap.FromFile(file),QrScanMode.Auto); // Per-thread QrReader instance — safe default var results = new QrReader().Read(input); foreach (QrResult result in results) { allResults.Add((Path.GetFileName(file), result.Value)); } } catch (Exception ex) {Interlocked.Increment(ref failCount);Console.Error.WriteLine($"[ERROR] {Path.GetFileName(file)}: {ex.Message}"); }});sw.Stop();Console.WriteLine($"Processed {files.Length} files in {sw.Elapsed.TotalSeconds:F1}s");Console.WriteLine($"QR codes found: {allResults.Count} | Failures: {failCount}");Console.WriteLine($"Throughput: {files.Length / sw.Elapsed.TotalSeconds:F1} files/sec");
using IronQr;
using IronQr.Enum;
using IronSoftware.Drawing;
using System.Collections.Concurrent;
using System.Diagnostics;
string[] files = Directory.GetFiles("qr-images/", "*.png");
var allResults = new ConcurrentBag<(string File, string Value)>();
int failCount = 0;
var sw = Stopwatch.StartNew();
Parallel.ForEach(files, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, file =>
{
try
{
var input = new QrImageInput(
AnyBitmap.FromFile(file),
QrScanMode.Auto);
// Per-thread QrReader instance — safe default
var results = new QrReader().Read(input);
foreach (QrResult result in results)
{
allResults.Add((Path.GetFileName(file), result.Value));
}
}
catch (Exception ex)
{
Interlocked.Increment(ref failCount);
Console.Error.WriteLine($"[ERROR] {Path.GetFileName(file)}: {ex.Message}");
}
});
sw.Stop();
Console.WriteLine($"Processed {files.Length} files in {sw.Elapsed.TotalSeconds:F1}s");
Console.WriteLine($"QR codes found: {allResults.Count} | Failures: {failCount}");
Console.WriteLine($"Throughput: {files.Length / sw.Elapsed.TotalSeconds:F1} files/sec");
C#
Output
The console displays a batch summary, including the number of files processed, processing time, QR codes found, any failures, and throughput. It then lists each filename with its decoded URL.
ConcurrentBag<T> gathers results from all threads without requiring locks. A thread-safe counter tracks failures, and using try-catch for each file ensures that one bad image does not interrupt the entire batch. This approach follows the error-isolation pattern described in the error handling how-to.
Set MaxDegreeOfParallelism to Environment.ProcessorCount to align with the number of CPU cores. Using additional threads increases overhead and does not improve performance, particularly for CPU-intensive ML models.
Combining Async and Parallel Processing
For high-volume pipelines, pair SemaphoreSlim with Task.WhenAll to bound concurrency. Unlike Parallel.ForEach, this pattern keeps I/O non-blocking while controlling how many decodes run at once, preventing thread pool saturation under large workloads.
Input
Four of the twenty QR code test images processed by the concurrent pipeline. Each image encodes a URL and is decoded in parallel using bounded concurrency via SemaphoreSlim.
Image 1 (Pipeline 1 of 20)
Image 2 (Pipeline 2 of 20)
Image 3 (Pipeline 3 of 20)
Image 4 (Pipeline 4 of 20)
using IronQr;using IronQr.Enum;using IronSoftware.Drawing;using System.Collections.Concurrent;using System.Diagnostics;string[] files = Directory.GetFiles("high-volume/", "*.png");var results = new ConcurrentBag<(stringFile, stringValue)>();int maxConcurrency = Environment.ProcessorCount;using var semaphore = new SemaphoreSlim(maxConcurrency);var sw = Stopwatch.StartNew();var tasks = files.Select(async file =>{ await semaphore.WaitAsync(); try { var bmp = AnyBitmap.FromFile(file); // Auto: runs ML detection plus a basic scan so result.Value is populated var input = new QrImageInput(bmp, QrScanMode.Auto); var qrResults = await new QrReader().ReadAsync(input); foreach (var qr in qrResults) { results.Add((Path.GetFileName(file), qr.Value)); } } catch (Exception ex) {Console.Error.WriteLine($"{{\"file\":\"{Path.GetFileName(file)}\",\"error\":\"{ex.Message}\"}}"); } finally { semaphore.Release(); }});awaitTask.WhenAll(tasks);sw.Stop();Console.WriteLine($"Pipeline complete: {results.Count} QR codes from {files.Length} files in {sw.Elapsed.TotalSeconds:F1}s");
using IronQr;
using IronQr.Enum;
using IronSoftware.Drawing;
using System.Collections.Concurrent;
using System.Diagnostics;
string[] files = Directory.GetFiles("high-volume/", "*.png");
var results = new ConcurrentBag<(string File, string Value)>();
int maxConcurrency = Environment.ProcessorCount;
using var semaphore = new SemaphoreSlim(maxConcurrency);
var sw = Stopwatch.StartNew();
var tasks = files.Select(async file =>
{
await semaphore.WaitAsync();
try
{
var bmp = AnyBitmap.FromFile(file);
// Auto: runs ML detection plus a basic scan so result.Value is populated
var input = new QrImageInput(bmp, QrScanMode.Auto);
var qrResults = await new QrReader().ReadAsync(input);
foreach (var qr in qrResults)
{
results.Add((Path.GetFileName(file), qr.Value));
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"{{\"file\":\"{Path.GetFileName(file)}\",\"error\":\"{ex.Message}\"}}");
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(tasks);
sw.Stop();
Console.WriteLine($"Pipeline complete: {results.Count} QR codes from {files.Length} files in {sw.Elapsed.TotalSeconds:F1}s");
C#
Output
The console displays a summary when the pipeline finishes: total QR codes decoded, number of source files, and elapsed time, followed by each filename and its decoded URL.
What is the advantage of using ReadAsync for QR code processing in IronQR?
ReadAsync allows QR code processing to be performed asynchronously, which prevents blocking the calling thread. This is particularly beneficial in UI applications, like WPF, where it prevents the UI from freezing during the processing.
How does IronQR utilize multithreading for batch processing?
IronQR uses Parallel.ForEach to distribute QR code processing across multiple CPU cores. This improves throughput by ensuring that all available processing power is used, thus speeding up batch operations.
Can IronQR handle processing QR codes in high-volume pipelines?
Yes, IronQR can handle high-volume pipelines by combining SemaphoreSlim with Task.WhenAll. This method allows for controlling concurrency, ensuring efficiency without overloading the system.
What image input format is required for using the ReadAsync method in IronQR?
The ReadAsync method in IronQR requires an image input constructed from an image bitmap as there is no direct file-path overload for the function.
Is there an async variant for QR code writing in IronQR?
No, the writing process in IronQR is synchronous. However, file I/O operations can be wrapped in File.WriteAllBytesAsync() to ensure they do not block the thread during writing.
What is the role of QrScanMode.Auto in IronQR?
QrScanMode.Auto in IronQR conducts both machine learning detection and a basic scan pass to populate the decoded value and QR type for each result. It's optimal when the encoded content needs to be extracted.
How does IronQR maintain thread safety while performing QR code reads in parallel?
IronQR recommends creating a separate QrReader instance for each parallel iteration since no explicit thread-safety guarantee is provided for shared reader instances, ensuring safe concurrent processing.
What method does IronQR recommend for error isolation during batch QR code processing?
IronQR suggests using a try-catch block around each image processing operation. This approach isolates errors, allowing processing to continue seamlessly in the event of a failure with a specific image.
How can concurrent results be gathered efficiently when using IronQR?
ConcurrentBag can be used to efficiently gather results from all threads in IronQR without the need for locks, making it suitable for concurrent QR code processing results collection.
Why is it important to set the MaxDegreeOfParallelism in IronQR's parallel processing?
Setting the MaxDegreeOfParallelism in IronQR's parallel processing to match the number of CPU cores ensures optimal resource utilization without unnecessary overhead, crucial for performance in CPU-bound tasks.
Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.