Cómo utilizar Async y el multithreading para operaciones con códigos QR en C

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

Leer código QR en un solo subproceso bloquea el subproceso de llamada durante el tiempo que dura cada decodificación de imagen.

En un controlador de botones WPF, esto congela la interfaz de usuario hasta que finaliza la decodificación. En un trabajo por lotes que procesa cientos de imágenes, deja inactivos los núcleos de la CPU cuando podrían estar trabajando en paralelo. El método ReadAsync de IronQR descarga las lecturas individuales a una tarea que se puede esperar, y el método estándar Read funciona con Parallel.ForEach y Task.WhenAll para el rendimiento por lotes.

Esta guía muestra cómo procesar códigos QR de forma asíncrona, distribuir lecturas por lotes entre los núcleos de la CPU y combinar ambos patrones para canalizaciones de gran volumen.

Inicio rápido: Procesar códigos QR de forma asíncrona

Carga una imagen y espera los resultados decodificados sin bloquear el subproceso de llamada.

  1. Instala IronQR con el Administrador de Paquetes NuGet

    PM > Install-Package IronQR
  2. Copie y ejecute este fragmento de código.

    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. Despliegue para probar en su entorno real

    Comienza a usar IronQR en tu proyecto hoy mismo con una prueba gratuita

    arrow pointer

Lectura asíncrona de códigos QR

ReadAsync devuelve una tarea que se puede esperar, lo que la hace compatible con los controladores de eventos de WPF/MAUI, las acciones de los controladores de ASP.NET o cualquier método asíncrono. La entrada debe construirse a partir de una imagen de mapa de bits; no hay sobrecarga de ruta de archivo.

El lado de escritura es síncrono y no tiene variantes asíncronas. Para evitar bloquear el hilo durante la E/S de archivos, envuelve el paso de guardado en File.WriteAllBytesAsync() utilizando los bytes sin procesar exportados desde el mapa de bits.

Entrada

Una tarjeta de evento con código QR escaneada y regenerada para demostrar el patrón asíncrono de lectura y escritura.

Codificación de códigos QR https://ironsoftware.com/event-badge utilizada como entrada de lectura asíncrona
: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

Resultado

El terminal muestra el tipo y el valor del código QR descodificado en el formato [QrType] Value y, a continuación, confirma que se ha guardado output-qr.png.

Salida del terminal que muestra el valor decodificado del [QR] y la confirmación de guardado de output-qr.png

QrScanMode.Auto ejecuta tanto la detección de ML como un escaneo básico, rellenando el valor descodificado y el tipo de QR en cada resultado. OnlyDetectionModel es más rápido, pero solo devuelve las coordenadas del cuadro delimitador, dejando el campo de valor vacío. Utilice Auto siempre que se necesite contenido codificado.


Procesamiento de códigos QR con multithreading

Para las imágenes que se pueden decodificar de forma independiente, Parallel.ForEach distribuye el trabajo entre los núcleos de CPU disponibles. Una instancia QrReader independiente por iteración es la opción predeterminada segura, ya que IronQR no ofrece ninguna garantía explícita de seguridad de subprocesos para las instancias de lector compartidas.

Entrada

Cuatro de las diez imágenes de prueba de QR utilizadas en el escaneo por lotes paralelo. Cada imagen codifica una URL y se lee desde la carpeta qr-images/ en tiempo de ejecución.

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

Resultado

La consola muestra un resumen del lote, que incluye el número de archivos procesados, el tiempo de procesamiento, los QR encontrados, los posibles errores y el rendimiento. A continuación, se enumeran todos los nombres de archivo con su URL descodificada.

Salida de terminal que muestra los resultados del procesamiento por lotes en paralelo: 10 archivos procesados, códigos QR encontrados, errores, rendimiento y URL descodificada por nombre de archivo

Descargue las 10 imágenes de entrada de códigos QR del lote de prueba (batch-qr-images.zip).

ConcurrentBag<t> recopila resultados de todos los subprocesos sin necesidad de bloqueos. Un contador seguro para subprocesos realiza un seguimiento de los fallos, y el uso de try-catch para cada archivo garantiza que una imagen defectuosa no interrumpa todo el lote. Este enfoque sigue el patrón de aislamiento de errores descrito en la guía práctica sobre gestión de errores.

Establezca MaxDegreeOfParallelism en Environment.ProcessorCount para que coincida con el número de núcleos de CPU. El uso de subprocesos adicionales aumenta la sobrecarga y no mejora el rendimiento, especialmente en el caso de modelos de aprendizaje automático que consumen muchos recursos de la CPU.


Combinación de procesamiento asíncrono y paralelo

Para flujos de trabajo de gran volumen, combine SemaphoreSlim con Task.WhenAll para limitar la concurrencia. A diferencia de Parallel.ForEach, este patrón mantiene la E/S sin bloqueo al tiempo que controla cuántas decodificaciones se ejecutan a la vez, lo que evita la saturación del grupo de subprocesos bajo grandes cargas de trabajo.

Entrada

Cuatro de las veinte imágenes de prueba de códigos QR procesadas por el canal de procesamiento concurrente. Cada imagen codifica una URL y se decodifica en paralelo utilizando concurrencia limitada a través de 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

Resultado

La consola muestra un resumen cuando finaliza el proceso: total de códigos QR descodificados, número de archivos de origen y tiempo transcurrido, seguido de cada nombre de archivo y su URL descodificada.

Salida de terminal que muestra los resultados del proceso: 20 códigos QR de 20 archivos con la URL descodificada por nombre de archivo

Descargue las 20 imágenes de entrada de códigos QR del proceso de prueba (high-volume-qr-images.zip).

Adapta el límite de semáforos al número de núcleos disponibles para el rendimiento, o redúcelo cuando la presión de memoria sea un problema con imágenes de gran tamaño.


Más información

Consulte las opciones de licencia cuando el proceso esté listo para la producción.

Curtis Chau
Escritor Técnico

Curtis Chau tiene una licenciatura en Ciencias de la Computación (Carleton University) y se especializa en el desarrollo front-end con experiencia en Node.js, TypeScript, JavaScript y React. Apasionado por crear interfaces de usuario intuitivas y estéticamente agradables, disfruta trabajando con frameworks modernos y creando manuales bien ...

Leer más
¿Listo para empezar?
Nuget Descargas 63,625 | Versión: 2026.4 recién lanzado
Still Scrolling Icon

¿Aún desplazándote?

¿Quieres una prueba rápida? PM > Install-Package IronQR
ejecuta una muestra observa cómo tu URL se convierte en un código QR.