Comment Lire un Code QR en Mode Async et Multithreading en C

This article was translated from English: Does it need improvement?
Translated
View the article in English

Le scan QR à un seul thread bloque le thread appelant pendant la durée de chaque décodage d'image.

Dans un gestionnaire de bouton WPF, cela fige l'interface utilisateur jusqu'à la fin du décodage. Lors du traitement par lots de centaines d'images, les cœurs du processeur restent inactifs alors qu'ils pourraient fonctionner en parallèle. La méthode ReadAsync d'IronQR décharge les lectures individuelles vers une tâche en attente, et la méthode standard Read fonctionne avec Parallel.ForEach et Task.WhenAll pour le débit par lots.

Ce guide explique comment traiter les codes QR de manière asynchrone, répartir les lectures par lots sur les cœurs du processeur et combiner les deux modèles pour les pipelines à haut volume.

Démarrage rapide : Traiter des QR Codes de manière asynchrone

Charger une image et attendre les résultats décodés sans bloquer le thread appelant.

  1. Installez IronQR avec le Gestionnaire de Packages NuGet

    PM > Install-Package IronQR
  2. Copiez et exécutez cet extrait de code.

    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. Déployez pour tester sur votre environnement de production.

    Commencez à utiliser IronQR dans votre projet dès aujourd'hui avec un essai gratuit

    arrow pointer

Lecture asynchrone des codes QR

ReadAsync renvoie une tâche pouvant être attendue, ce qui la rend compatible avec les gestionnaires d'événements WPF/MAUI, les actions de contrôleur ASP.NET ou toute méthode asynchrone. L'entrée doit être construite à partir d'une image bitmap ; il n'y a pas de surcharge de chemin de fichier.

L'écriture est synchrone et ne comporte aucune variante asynchrone. Pour éviter de bloquer le thread pendant les E/S de fichiers, enveloppez l'étape d'enregistrement dans File.WriteAllBytesAsync() en utilisant les octets bruts exportés de la bitmap.

Entrée

Un badge d'événement à code QR scanné et régénéré pour illustrer le modèle de lecture et d'écriture asynchrone.

Encodage du code QR https://ironsoftware.com/event-badge utilisé comme entrée de lecture asynchrone
: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

Sortie

Le terminal affiche le type et la valeur du QR décodé au format [QrType] Value, puis confirme que output-qr.png a été enregistré.

Affichage du terminal : valeur décodée du code QR et confirmation d'enregistrement du fichier output-qr.png.

QrScanMode.Auto exécute à la fois la détection ML et une passe de numérisation de base, en remplissant la valeur décodée et le type QR dans chaque résultat. OnlyDetectionModel est plus rapide mais ne renvoie que les coordonnées de la boîte englobante, laissant le champ de valeur vide. Utilisez Auto chaque fois que le contenu encodé est nécessaire.


Traitement des codes QR avec multithreading

Pour les images qui peuvent être décodées indépendamment, Parallel.ForEach répartit le travail sur les cœurs de processeur disponibles. Une instance QrReader séparée par itération est la valeur par défaut sûre, car IronQR ne fournit aucune garantie explicite de sécurité des threads pour les instances de lecteur partagées.

Entrée

Quatre des dix images de test de code QR utilisées dans le scan par lots parallèle. Chaque image encode une URL et est lue à partir du dossier qr-images/ au moment de l'exécution.

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

Sortie

La console affiche un résumé du traitement par lots, incluant le nombre de fichiers traités, le temps de traitement, les codes QR trouvés, les éventuelles erreurs et le débit. Il liste ensuite chaque nom de fichier avec son URL décodée.

Sortie du terminal affichant les résultats du traitement par lots parallèle : 10 fichiers traités, codes QR trouvés, échecs, débit et URL décodée par nom de fichier

Téléchargez les 10 images d'entrée de code QR du lot de test (batch-qr-images.zip).

ConcurrentBag<t> rassemble les résultats de tous les threads sans nécessiter de verrous. Un compteur thread-safe suit les échecs, et l'utilisation de try-catch pour chaque fichier garantit qu'une image défectueuse n'interrompt pas le traitement par lots entier. Cette approche suit le modèle d'isolation des erreurs décrit dans le guide pratique de gestion des erreurs .

Définissez MaxDegreeOfParallelism à Environment.ProcessorCount pour correspondre au nombre de cœurs du processeur. L'utilisation de threads supplémentaires augmente la surcharge et n'améliore pas les performances, en particulier pour les modèles d'apprentissage automatique gourmands en ressources CPU.


Combinaison du traitement asynchrone et parallèle

Pour les pipelines à volume élevé, associez SemaphoreSlim avec Task.WhenAll pour limiter la concurrence. Contrairement à Parallel.ForEach, ce modèle maintient les E/S non bloquantes tout en contrôlant le nombre de décodages exécutés simultanément, évitant ainsi la saturation du pool de threads sous des charges de travail importantes.

Entrée

Quatre des vingt images de test de code QR ont été traitées par le pipeline concurrent. Chaque image encode une URL et est décodée en parallèle à l'aide d'une concurrence limitée 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

Sortie

La console affiche un résumé une fois le pipeline terminé : nombre total de codes QR décodés, nombre de fichiers sources et temps écoulé, suivis du nom de chaque fichier et de son URL décodée.

Sortie du terminal affichant les résultats du pipeline : 20 codes QR provenant de 20 fichiers, avec l'URL décodée pour chaque nom de fichier

Téléchargez les 20 images d'entrée de code QR du pipeline de test (high-volume-qr-images.zip).

Adaptez la limite des sémaphores au nombre de cœurs disponibles pour optimiser le débit, ou réduisez-la lorsque la pression sur la mémoire est un problème avec les images volumineuses.


Lecture supplémentaire

Voir les options de licence lorsque le pipeline est prêt pour la production.

Curtis Chau
Rédacteur technique

Curtis Chau détient un baccalauréat en informatique (Université de Carleton) et se spécialise dans le développement front-end avec expertise en Node.js, TypeScript, JavaScript et React. Passionné par la création d'interfaces utilisateur intuitives et esthétiquement plaisantes, Curtis aime travailler avec des frameworks modernes ...

Lire la suite
Prêt à commencer?
Nuget Téléchargements 63,625 | Version : 2026.4 vient de sortir
Still Scrolling Icon

Vous faites encore défiler ?

Vous voulez une preuve rapidement ? PM > Install-Package IronQR
exécuter un échantillon regarder votre URL devenir un code QR.