IRONSOFTWAREHOME

How to Use Async and Multithreading for QR Code Operations in C#

Curtis Chau
Curtis Chau
Updated: March 6, 2026

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. 1Install IronQR with NuGet Package Manager

    PM > Install-Package IronQR

  2. 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);
    C#
  3. 3Deploy 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
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.

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

Image 1 (Batch 1 of 10)

Batch QR code input image 2 of 10 encoding https://ironsoftware.com/batch-2

Image 2 (Batch 2 of 10)

Batch QR code input image 3 of 10 encoding https://ironsoftware.com/batch-3

Image 3 (Batch 3 of 10)

Batch QR code input image 4 of 10 encoding https://ironsoftware.com/batch-4

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

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

Image 1 (Pipeline 1 of 20)

Pipeline QR code input image 2 of 20

Image 2 (Pipeline 2 of 20)

Pipeline QR code input image 3 of 20

Image 3 (Pipeline 3 of 20)

Pipeline QR code input image 4 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<(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.

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 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
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 74,386Version:2026.9just released

Get your free 30-day Trial Key instantly.
No credit card or account creation required
C# NuGet Library for PDF
Install with NuGet

Version: 2026.9

PM > Install-Package IronQR
nuget.org/packages/IronQR/
  1. In Solution Explorer, right-click References, Manage NuGet Packages
  2. Select Browse and search "IronQR"
  3. Select the package and install
C# PDF DLL
Download DLL

Version: 2026.9

  1. Download and unzip IronQR to a location such as ~/Libs within your Solution directory
  2. In Visual Studio Solution Explorer, right click References. Select Browse, "IronQR.dll"

Licenses from $999

Key in blue circle

Get your free 30-day Trial Key instantly.

Your trial license will be sent to your email address

No limitations. 100% unlocked. No credit card.

bullet_checkedNo credit card or account creation requiredNo limitations. 100% unlocked. No credit card.
  • Logo Aetna
  • Logo NASA
  • Logo GE
  • Logo Porsche
  • Logo USDA
  • Logo Qatar
Join Millions of Engineers who’ve tried IronPDF
Book your free Live Demo
Booking Badge

Trusted by Millions of Engineers Worldwide

Iron Software's customer logos
Get Your No-Obligation Consult
Complete the form below or email sales@ironsoftware.com
Your details will always be kept confidential.
Trusted by Millions of Engineers Worldwide
Iron Software's customer logos
Get your free 30-day Trial Key instantly.
No credit card or account creation required