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.

  1. Install IronQR with NuGet Package Manager

    PM > Install-Package IronQR
  2. 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);
  3. Deploy to test on your live environment

    Start using IronQR in your project today with a free trial

    arrow pointer

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.

QR code encoding https://ironsoftware.com/event-badge used as async read input
: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)
$vbLabelText   $csharpLabel

Output

The terminal displays the decoded QR type and value in the format [QrType] Value, then confirms that output-qr.png was saved.

Terminal output showing [QRCode] decoded value and output-qr.png save confirmation

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.

Batch QR code input image 1 of 10 encoding https://ironsoftware.com/batch-1
Batch QR code input image 2 of 10 encoding https://ironsoftware.com/batch-2
Batch QR code input image 3 of 10 encoding https://ironsoftware.com/batch-3
Batch QR code input image 4 of 10 encoding https://ironsoftware.com/batch-4
: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")
$vbLabelText   $csharpLabel

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.

Terminal output showing parallel batch results: 10 files processed, QR codes found, failures, throughput, and decoded URL per filename

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.

Pipeline QR code input image 1 of 20
Pipeline QR code input image 2 of 20
Pipeline QR code input image 3 of 20
Pipeline QR code input image 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
$vbLabelText   $csharpLabel

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.

Terminal output showing pipeline results: 20 QR codes from 20 files with decoded URL per filename

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

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.

Curtis Chau
Technical Writer

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.

...

Read More
Ready to Get Started?
Nuget Downloads 66,093 | Version: 2026.5 just released
Still Scrolling Icon

Still Scrolling?

Want proof fast? PM > Install-Package IronQR
run a sample watch your URL become a QR code.