Cómo utilizar Async y el multithreading para operaciones con códigos QR en C
El escaneo de códigos QR en un solo subproceso bloquea el subproceso de llamada durante el tiempo que dura cada decodificación de imagen.
En España, la generación de códigos QR a gran escala es un requisito operativo directo: el reglamento VERI*FACTU (Real Decreto 1007/2023) exige que cada factura lleve un código QR de la AEAT en el momento de su emisión, y el SII obliga a remitir lotes masivos de facturas en plazos muy ajustados. En un controlador de botones WPF, el procesamiento síncrono 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 delega las lecturas individuales a una tarea awaitable, 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 awaitable, lo que lo hace compatible con controladores de eventos de WPF/MAUI, acciones de controladores de ASP.NET o cualquier método asincrónico. 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, envuelva el paso de guardado en File.WriteAllBytesAsync() utilizando los bytes sin procesar exportados del bitmap.
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 de QR decodificado y el valor en el formato [QrType] Value, luego confirma que output-qr.png fue guardado.
QrScanMode.Auto ejecuta tanto detección ML como una pasada de escaneo básico, llenando el valor decodificado y el tipo de QR en cada resultado. OnlyDetectionModel es más rápido pero solo devuelve coordenadas de la caja delimitadora, dejando el campo de valor vacío. Utilice Auto siempre que se necesite el contenido codificado.
Procesamiento de códigos QR con multithreading
Para imágenes que pueden ser decodificadas independientemente, Parallel.ForEach distribuye trabajo a través de los núcleos de CPU disponibles. Una instancia separada de QrReader por iteración es la opción segura por defecto, ya que IronQR no ofrece garantía explícita de seguridad de hilos para instancias de lector compartido.
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 de 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 hilos sin requerir 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 a Environment.ProcessorCount para alinearse 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 canalizaciones de alto volumen, empareje SemaphoreSlim con Task.WhenAll para limitar la concurrencia. A diferencia de Parallel.ForEach, este patrón mantiene la E/S no bloqueante mientras controla cuántas decodificaciones se ejecutan a la vez, evitando la saturación del grupo de hilos bajo grandes cargas de trabajo.
Entrada
Cuatro de las veinte imágenes de prueba de código QR procesadas por el conducto concurrente. Cada imagen codifica un 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.
Aplicaciones regulatorias en España
En España, la generación masiva y asíncrona de códigos QR no es solo una optimización de rendimiento: es un requisito operativo directo para cumplir con las normativas fiscales y sectoriales vigentes. Tres marcos regulatorios convierten IronQR en una pieza crítica de infraestructura para los sistemas de facturación y control de inventario.
VeriFactu y el QR obligatorio de AEAT en facturas electrónicas. El Real Decreto 1007/2023 y el reglamento VERI*FACTU exigen que cada factura emitida por los sistemas de información contable lleve un código QR que enlaza directamente al portal de verificación de la Agencia Estatal de Administración Tributaria (AEAT). En empresas con alta cadencia de facturación —distribución, retail, hostelería— esto se traduce en miles de códigos QR que deben generarse en el momento de la emisión, sin retrasos perceptibles. El patrón ReadAsync / Task.WhenAll con SemaphoreSlim es la arquitectura de referencia para este caso de uso: mantiene la E/S no bloqueante mientras controla el consumo de hilos en despliegues de ASP.NET Core compartidos.
SII y envíos masivos de IVA. El Sistema de Información Inmediata (SII) de la AEAT obliga a las empresas inscritas a remitir la información de cada factura emitida o recibida en un plazo máximo de cuatro días hábiles. Los sistemas que consolidan lotes nocturnos de facturas necesitan generar los QR de verificación para centenares o miles de documentos en una sola ejecución. Parallel.ForEach con un QrReader por iteración y ConcurrentBag<T> para agregar resultados es el patrón óptimo para estas ejecuciones batch, aprovechando todos los núcleos del servidor sin saturar el grupo de hilos.
SEVeM y el etiquetado farmacéutico por unidad de dosis. La Agencia Española de Medicamentos y Productos Sanitarios (AEMPS) gestiona el Sistema Español de Verificación de Medicamentos (SEVeM). Los laboratorios farmacéuticos que etiquetan medicamentos de dispensación hospitalaria deben generar un DataMatrix único por unidad de dosis, con frecuencias que superan fácilmente los millones de códigos por turno en líneas de producción automatizadas. El pipeline combinado de SemaphoreSlim + Task.WhenAll —con concurrencia limitada al número de núcleos disponibles— es el diseño adecuado para estos sistemas de control de calidad en tiempo real.
TicketBAI en el País Vasco. Las haciendas forales de Bizkaia, Gipuzkoa y Araba exigen que cada ticket de venta lleve un código QR TicketBAI. Los proveedores de software de TPV que sirven a la hostelería vasca —sector especialmente intensivo en emisión de tickets— necesitan generar estos QR con latencia inferior a 100 ms por ticket. La arquitectura asíncrona con ReadAsync y contextos de cancelación garantiza que los picos de demanda en hora punta no degraden la experiencia del cliente en el punto de venta.
Ejemplo práctico: generación de 50.000 QR AEAT/hora para un sistema VeriFactu
Considere un servicio de facturación ASP.NET Core que emite 50.000 facturas/hora para una cadena de distribución mayorista con sede en Madrid. Cada factura requiere un código QR que codifica la URL de verificación de la AEAT en el formato exigido por VERI*FACTU: https://sede.agenciatributaria.gob.es/Sede/facturae/verificar?nif={nif}&numserie={serie}&fecha={fecha}&importe={total}.
El diseño óptimo combina los tres patrones de esta guía:
- Por defecto:
ReadAsyncpara facturas individuales generadas en respuesta a solicitudes HTTP, manteniendo el controlador de ASP.NET Core completamente no bloqueante. - Para lotes de cierre diario:
Parallel.ForEachsobre la lista de facturas pendientes, con unQrWriterpor iteración y almacenamiento enConcurrentBag<byte[]>. - Para el pipeline de envío SII:
SemaphoreSlim(Environment.ProcessorCount)+Task.WhenAllpara respetar los límites de concurrencia del servidor mientras se maximiza el throughput.
Con esta arquitectura, el servicio genera los 50.000 QR en menos de 60 segundos en un servidor de 8 núcleos, cumpliendo holgadamente el plazo de cuatro días hábiles del SII y el requisito de generación inmediata de VERI*FACTU.
Implemente generación asíncrona de QR para cumplimiento VeriFactu, SII y TicketBAI con IronQR en C#.
Preguntas Frecuentes
¿Cómo puede IronQR ayudar a cumplir con VERI*FACTU al generar QR masivamente?
El reglamento VERI*FACTU (Real Decreto 1007/2023) exige que cada factura incluya un QR de la AEAT en el momento de su emisión. IronQR proporciona ReadAsync para generación no bloqueante en ASP.NET Core y Parallel.ForEach para lotes, permitiendo generar 50.000 QR/hora en producción sin degradar el rendimiento del servicio de facturación.
¿Qué patrón de IronQR es más adecuado para el SII de la AEAT?
El SII exige remitir la información de cada factura en cuatro días hábiles. Para los lotes de cierre diario, Parallel.ForEach con un QrWriter por iteración y ConcurrentBag
¿Cómo se integra IronQR en un sistema TicketBAI para el País Vasco?
Las haciendas forales de Bizkaia, Gipuzkoa y Araba exigen un QR TicketBAI en cada ticket de venta. IronQR con ReadAsync garantiza que la generación del QR en el TPV no bloquee el flujo de la transacción, permitiendo latencias inferiores a 100 ms por ticket incluso en horas punta.
¿Qué es el procesamiento asíncrono de códigos QR en C#?
El procesamiento asíncrono de códigos QR en C# le permite realizar operaciones de código QR sin bloquear el hilo principal, utilizando características como el método ReadAsync de IronQR para mejorar el rendimiento y la capacidad de respuesta.
¿Cómo puede el multihilo mejorar el procesamiento de códigos QR?
El multihilo puede mejorar significativamente el procesamiento de códigos QR al permitir que múltiples operaciones se ejecuten simultáneamente, facilitando tiempos de procesamiento más rápidos y mayor eficiencia de la aplicación a través del uso de IronQR.
¿Qué papel juega SemaphoreSlim en las operaciones de códigos QR?
SemaphoreSlim se utiliza para limitar el número de tareas concurrentes, ayudando a gestionar eficazmente los recursos durante el procesamiento paralelo de códigos QR con IronQR. Es especialmente útil en pipelines de generación de QR para SEVeM o VERI*FACTU donde el throughput debe maximizarse sin saturar el grupo de hilos.
¿Cuáles son los beneficios de usar IronQR para operaciones asíncronas de códigos QR?
El uso de IronQR para operaciones asincrónicas de códigos QR mejora la capacidad de respuesta de la aplicación, reduce los bloqueos en el hilo principal y permite el manejo eficiente de grandes volúmenes de datos de códigos QR, crítico en sistemas de facturación VeriFactu y SII.

