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.
Quickstart: Process QR Codes Asynchronously
Load an image and await the decoded results without blocking the calling thread.
-
Install IronQR with NuGet Package Manager
PM > Install-Package IronQR -
Copy 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); -
Deploy to test on your live environment
Start using IronQR in your project today with a free trial
Minimal Workflow (5 steps)
- Download the IronQR C# library for async QR code processing
- Use
ReadAsyncfor non-blocking single reads - Use
Parallel.ForEachfor CPU-bound batch processing - Combine with
SemaphoreSlimfor 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.
:path=/static-assets/qr/content-code-examples/how-to/async-and-multithreading/async-read-write.cs
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.OnlyDetectionModel);
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);
Imports IronQr
Imports IronQr.Enum
Imports IronSoftware.Drawing
Imports System.IO
' --- Async read: non-blocking QR decode ---
Dim inputBmp = AnyBitmap.FromFile("event-badge.png")
Dim imageInput = New QrImageInput(inputBmp, QrScanMode.OnlyDetectionModel)
Dim reader = New QrReader()
Dim results As IEnumerable(Of QrResult) = Await reader.ReadAsync(imageInput)
For Each result As QrResult In results
Console.WriteLine($"[{result.QrType}] {result.Value}")
Next
' --- Async-wrapped save: QrWriter.Write() and QrCode.Save() are synchronous ---
Dim qrCode As QrCode = QrWriter.Write("https://ironsoftware.com")
Dim qrImage As AnyBitmap = qrCode.Save()
' Save the bitmap bytes asynchronously (not an IronQR API — standard .NET async I/O)
Dim pngBytes As Byte() = qrImage.ExportBytes()
Await File.WriteAllBytesAsync("output-qr.png", pngBytes)
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)
:path=/static-assets/qr/content-code-examples/how-to/async-and-multithreading/parallel-batch.cs
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.OnlyDetectionModel);
// 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");
Imports IronQr
Imports IronQr.Enum
Imports IronSoftware.Drawing
Imports System.Collections.Concurrent
Imports System.Diagnostics
Dim files As String() = Directory.GetFiles("qr-images/", "*.png")
Dim allResults As New ConcurrentBag(Of (File As String, Value As String))()
Dim failCount As Integer = 0
Dim sw As Stopwatch = Stopwatch.StartNew()
Parallel.ForEach(files, New ParallelOptions With {.MaxDegreeOfParallelism = Environment.ProcessorCount}, Sub(file)
Try
Dim input As New QrImageInput(AnyBitmap.FromFile(file), QrScanMode.OnlyDetectionModel)
' Per-thread QrReader instance — safe default
Dim results = New QrReader().Read(input)
For Each result As QrResult In results
allResults.Add((Path.GetFileName(file), result.Value))
Next
Catch ex As Exception
Interlocked.Increment(failCount)
Console.Error.WriteLine($"[ERROR] {Path.GetFileName(file)}: {ex.Message}")
End Try
End Sub)
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")
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.
Download all 10 test batch QR code input images (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.
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)
:path=/static-assets/qr/content-code-examples/how-to/async-and-multithreading/semaphore-pipeline.cs
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);
// OnlyDetectionModel: fastest per-image — critical at scale
var input = new QrImageInput(bmp, QrScanMode.OnlyDetectionModel);
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");
Imports IronQr
Imports IronQr.Enum
Imports IronSoftware.Drawing
Imports System.Collections.Concurrent
Imports System.Diagnostics
Module Program
Sub Main()
Dim files As String() = Directory.GetFiles("high-volume/", "*.png")
Dim results As New ConcurrentBag(Of (File As String, Value As String))()
Dim maxConcurrency As Integer = Environment.ProcessorCount
Using semaphore As New SemaphoreSlim(maxConcurrency)
Dim sw As Stopwatch = Stopwatch.StartNew()
Dim tasks = files.Select(Function(file) Task.Run(Async Function()
Await semaphore.WaitAsync()
Try
Dim bmp = AnyBitmap.FromFile(file)
' OnlyDetectionModel: fastest per-image — critical at scale
Dim input As New QrImageInput(bmp, QrScanMode.OnlyDetectionModel)
Dim qrResults = Await (New QrReader()).ReadAsync(input)
For Each qr In qrResults
results.Add((Path.GetFileName(file), qr.Value))
Next
Catch ex As Exception
Console.Error.WriteLine($"{{""file"":""{Path.GetFileName(file)}"",""error"":""{ex.Message}""}}")
Finally
semaphore.Release()
End Try
End Function))
Task.WhenAll(tasks).Wait()
sw.Stop()
Console.WriteLine($"Pipeline complete: {results.Count} QR codes from {files.Length} files in {sw.Elapsed.TotalSeconds:F1}s")
End Using
End Sub
End Module
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.
Download all 20 test pipeline QR code input images (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: scan mode comparison with code samples.
- Reading QR Codes How-To: input construction and basic read patterns.
- QR Code Generator Tutorial: generation with styling.
- QrReader API Reference: method signatures and remarks.
- QrWriter API Reference: all write overloads.
- Error Handling How-To: per-file error isolation and logging patterns.
View licensing options when the pipeline is ready for production.
Frequently Asked Questions
What is asynchronous QR code processing in C#?
Asynchronous QR code processing in C# allows you to perform QR code operations without blocking the main thread, using features like IronQR's ReadAsync method to enhance performance and responsiveness.
How can multithreading improve QR code processing?
Multithreading can significantly enhance QR code processing by allowing multiple operations to run concurrently, facilitating faster processing times and improved application efficiency through the use of IronQR.
What is the ReadAsync method in IronQR?
The ReadAsync method in IronQR enables asynchronous reading of QR codes, allowing your C# application to handle QR code data without delaying other tasks.
How does Parallel.ForEach help in QR code processing?
Parallel.ForEach allows for processing multiple QR codes simultaneously by distributing tasks across multiple threads, which can be efficiently utilized with IronQR to speed up QR code operations.
What role does SemaphoreSlim play in QR code operations?
SemaphoreSlim is used to limit the number of concurrent tasks, helping manage resources effectively during parallel processing of QR codes with IronQR.
Can IronQR be used for processing QR codes in a bounded pipeline?
Yes, IronQR can be used to process QR codes in a bounded pipeline, using constructs like SemaphoreSlim to control concurrency and resource allocation effectively.
What are the benefits of using IronQR for async QR code operations?
Using IronQR for async QR code operations improves application responsiveness, reduces blocking on the main thread, and allows efficient handling of large volumes of QR code data.
Is it possible to run QR code operations in parallel using IronQR?
Yes, IronQR supports parallel processing of QR code operations, allowing you to leverage multithreading capabilities in C# for faster and more efficient QR code handling.
How does IronQR enhance QR code processing in C# applications?
IronQR enhances QR code processing by providing robust asynchronous and multithreading capabilities, reducing processing times and improving the scalability of C# applications.
What C# features complement the use of IronQR for QR code processing?
C# features such as async/await, Parallel.ForEach, and SemaphoreSlim complement IronQR, providing a framework for efficient and high-performance QR code processing.

