Cómo utilizar Async y el multithreading para operaciones con códigos QR en C
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.
-
Instala IronQR con el Administrador de Paquetes NuGet
PM > Install-Package IronQR -
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); -
Despliegue para probar en su entorno real
Comienza a usar IronQR en tu proyecto hoy mismo con una prueba gratuita
Flujo de trabajo mínimo (5 pasos)
- Descarga la biblioteca IronQR C# para el procesamiento asíncrono de códigos QR
- Utilice
ReadAsyncpara lecturas únicas sin bloqueo - Utilice
Parallel.ForEachpara el procesamiento por lotes limitado por la CPU - Combínalo con
SemaphoreSlimpara obtener pipelines de concurrencia limitada - Recopila los resultados de
IEnumerablee imprime los valores descodificados
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.
: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)
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.
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.
Imagen 1 (Lote 1 de 10)
Imagen 2 (Lote 2 de 10)
Imagen 3 (Lote 3 de 10)
Imagen 4 (Lote 4 de 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")
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.
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.
Imagen 1 (Pipeline 1 de 20)
Imagen 2 (Pipeline 2 de 20)
Imagen 3 (Pipeline 3 de 20)
Imagen 4 (Pipeline 4 de 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
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.
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
- Ejemplo de escaneo ML: comparación del modo de escaneo con ejemplos de código.
- Guía práctica para la lectura de códigos QR: construcción de entradas y patrones de lectura básicos.
- Tutorial del generador de códigos QR: generación con estilo.
- Referencia de la API de QrReader: firmas de métodos y comentarios.
- Referencia de la API de QrWriter: todas las sobrecargas de escritura.
- Guía práctica sobre el manejo de errores: aislamiento de errores por archivo y patrones de registro.
Consulte las opciones de licencia cuando el proceso esté listo para la producción.

