# 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.
*as-heading:2(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.
```csharp
:title=Quickstart
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);
```
<div class="hsg-featured-snippet">
<h3>Flujo de trabajo mínimo (5 pasos)</h3>
<ol>
<li><a class="js-modal-open" data-modal-id="trial-license-after-download" href="https://nuget.org/packages/IronQR/">Descarga la biblioteca IronQR C# para el procesamiento asíncrono de códigos QR</a></li>
<li>Utilice <code>ReadAsync</code> para lecturas únicas sin bloqueo</li>
<li>Utilice <code>Parallel.ForEach</code> para el procesamiento por lotes limitado por la CPU</li>
<li>Combínalo con <code>SemaphoreSlim</code> para obtener pipelines de concurrencia limitada</li>
<li>Recopila los resultados de <code>IEnumerable<qrresult></code> e imprime los valores descodificados</li>
</ol>
</div>
## 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.
<div class="content-img-align-center">
<div class="center-image-wrapper" style="max-width: 300px;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-async-event-badge.webp"
alt="Codificación de códigos QR https://ironsoftware.com/event-badge utilizada como entrada de lectura asíncrona"
class="img-responsive add-shadow" />
</div>
</div>
```cs
:path=/static-assets/qr/content-code-examples/how-to/async-and-multithreading/async-read-write.cs
```
### 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.
<div class="content-img-align-center">
<div class="center-image-wrapper">
<img src="/static-assets/qr/how-to/async-and-multithreading/test1-async-out.webp" alt="Salida del terminal que muestra el valor decodificado del [QR] y la confirmación de guardado de output-qr.png" class="img-responsive add-shadow" />
</div>
</div>
`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.
<div class="competitors-section__wrapper-even-1">
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-batch-qr-1.webp"
alt="Imagen de entrada de código QR por lotes 1 de 10 que codifica https://ironsoftware.com/batch-1"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Imagen 1 (Lote 1 de 10)
</p>
</div>
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-batch-qr-2.webp"
alt="Imagen de entrada de código QR por lotes 2 de 10 que codifica https://ironsoftware.com/batch-2"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Imagen 2 (Lote 2 de 10)
</p>
</div>
</div>
<div class="competitors-section__wrapper-even-1">
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-batch-qr-3.webp"
alt="Imagen de entrada de código QR por lotes 3 de 10 que codifica https://ironsoftware.com/batch-3"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Imagen 3 (Lote 3 de 10)
</p>
</div>
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-batch-qr-4.webp"
alt="Imagen de entrada de código QR por lotes 4 de 10 que codifica https://ironsoftware.com/batch-4"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Imagen 4 (Lote 4 de 10)
</p>
</div>
</div>
```cs
:path=/static-assets/qr/content-code-examples/how-to/async-and-multithreading/parallel-batch.cs
```
### 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.
<div class="content-img-align-center">
<div class="center-image-wrapper">
<img src="/static-assets/qr/how-to/async-and-multithreading/test2-async-output.webp" alt="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" class="img-responsive add-shadow" />
</div>
</div>
[Descargue las 10 imágenes de entrada de códigos QR del lote de prueba (batch-qr-images.zip).](/static-assets/qr/how-to/async-and-multithreading/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](https://ironsoftware.com/csharp/qr/how-to/detailed-error-messages/).
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`.
<div class="competitors-section__wrapper-even-1">
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-pipeline-qr-1.webp"
alt="Imagen de entrada de código QR de la canalización 1 de 20"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Imagen 1 (Pipeline 1 de 20)
</p>
</div>
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-pipeline-qr-2.webp"
alt="Imagen de entrada de código QR de la canalización 2 de 20"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Imagen 2 (Pipeline 2 de 20)
</p>
</div>
</div>
<div class="competitors-section__wrapper-even-1">
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-pipeline-qr-3.webp"
alt="Imagen de entrada de código QR de la canalización 3 de 20"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Imagen 3 (Pipeline 3 de 20)
</p>
</div>
<div class="competitors__card" style="width: 47%;">
<img src="/static-assets/qr/how-to/async-and-multithreading/input-pipeline-qr-4.webp"
alt="Imagen de entrada de código QR de la canalización 4 de 20"
class="img-responsive add-shadow" style="max-width: 200px;" />
<p class="competitors__download-link" style="color: #181818; font-style: italic;">
Imagen 4 (Pipeline 4 de 20)
</p>
</div>
</div>
```cs
:path=/static-assets/qr/content-code-examples/how-to/async-and-multithreading/semaphore-pipeline.cs
```
### 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.
<div class="content-img-align-center">
<div class="center-image-wrapper">
<img src="/static-assets/qr/how-to/async-and-multithreading/test3async-output.webp" alt="Salida de terminal que muestra los resultados del proceso: 20 códigos QR de 20 archivos con la URL descodificada por nombre de archivo" class="img-responsive add-shadow" />
</div>
</div>
[Descargue las 20 imágenes de entrada de códigos QR del proceso de prueba (high-volume-qr-images.zip).](/static-assets/qr/how-to/async-and-multithreading/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
- [Ejemplo de escaneo ML](https://ironsoftware.com/csharp/qr/examples/read-qr-with-machine-learning/): comparación del modo de escaneo con ejemplos de código.
- [Guía práctica para la lectura de códigos QR](https://ironsoftware.com/csharp/qr/how-to/read-qr-codes-from-image/): construcción de entradas y patrones de lectura básicos.
- [Tutorial del generador de códigos QR](https://ironsoftware.com/csharp/qr/tutorials/csharp-qr-code-generator/): generación con estilo.
- [Referencia de la API de QrReader](https://ironsoftware.com/csharp/qr/object-reference/api/IronQr.QrReader.html): firmas de métodos y comentarios.
- [Referencia de la API de QrWriter](https://ironsoftware.com/csharp/qr/object-reference/api/IronQr.QrWriter.html): todas las sobrecargas de escritura.
- [Guía práctica sobre el manejo de errores](https://ironsoftware.com/csharp/qr/how-to/detailed-error-messages/): aislamiento de errores por archivo y patrones de registro.
[Consulte las opciones de licencia](https://ironsoftware.com/csharp/qr/licensing/) 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:
1. **Por defecto**: `ReadAsync` para facturas individuales generadas en respuesta a solicitudes HTTP, manteniendo el controlador de ASP.NET Core completamente no bloqueante.
2. **Para lotes de cierre diario**: `Parallel.ForEach` sobre la lista de facturas pendientes, con un `QrWriter` por iteración y almacenamiento en `ConcurrentBag<byte[]>`.
3. **Para el pipeline de envío SII**: `SemaphoreSlim(Environment.ProcessorCount)` + `Task.WhenAll` para 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.
<a href="https://ironsoftware.com/csharp/qr/how-to/async-multithreading/" class="code_content__related-link__doc-cta-link">Implemente generación asíncrona de QR para cumplimiento VeriFactu, SII y TicketBAI con IronQR en C#.</a>
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.
1Install IronQR with NuGet Package Manager
PM > Install-Package IronQR
Install-Package IronQR
2Copie 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);
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#
3Despliegue para probar en su entorno real
Comienza a usar IronQR en tu proyecto hoy mismo con una prueba gratuita
Utilice ReadAsync para lecturas únicas sin bloqueo
Utilice Parallel.ForEach para el procesamiento por lotes limitado por la CPU
Combínalo con SemaphoreSlim para obtener pipelines de concurrencia limitada
Recopila los resultados de IEnumerable<qrresult> e 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.
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();awaitFile.WriteAllBytesAsync("output-qr.png", pngBytes);
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#
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)
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<(stringFile, stringValue)>();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");
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#
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.
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)
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<(stringFile, stringValue)>();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(); }});awaitTask.WhenAll(tasks);sw.Stop();Console.WriteLine($"Pipeline complete: {results.Count} QR codes from {files.Length} files in {sw.Elapsed.TotalSeconds:F1}s");
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#
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.
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: ReadAsync para facturas individuales generadas en respuesta a solicitudes HTTP, manteniendo el controlador de ASP.NET Core completamente no bloqueante.
Para lotes de cierre diario: Parallel.ForEach sobre la lista de facturas pendientes, con un QrWriter por iteración y almacenamiento en ConcurrentBag<byte[]>.
Para el pipeline de envío SII: SemaphoreSlim(Environment.ProcessorCount) + Task.WhenAll para 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.
¿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é es el método ReadAsync en IronQR?
El método ReadAsync en IronQR permite la lectura asíncrona de códigos QR, permitiendo que su aplicación C# maneje datos de código QR sin retrasar otras tareas.
¿Cómo ayuda Parallel.ForEach en el procesamiento de códigos QR?
Parallel.ForEach permite procesar múltiples códigos QR simultáneamente distribuyendo tareas en varios hilos, lo cual puede ser utilizado eficientemente con IronQR para acelerar las operaciones de código QR.
¿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.
¿Puede IronQR ser utilizado para procesar códigos QR en un canal acotado?
Sí, IronQR puede ser utilizado para procesar códigos QR en un canal acotado, usando constructos como SemaphoreSlim para controlar la concurrencia y la asignación de recursos de manera efectiva.
¿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.
¿Es posible ejecutar operaciones de códigos QR en paralelo usando IronQR?
Sí, IronQR admite el procesamiento paralelo de operaciones de códigos QR, lo que permite aprovechar las capacidades de multithreading en C# para un manejo más rápido y eficiente.
¿Cómo mejora IronQR el procesamiento de códigos QR en aplicaciones C#?
IronQR mejora el procesamiento de códigos QR al proporcionar capacidades robustas de asincronía y multithreading, reduciendo los tiempos de procesamiento y mejorando la escalabilidad de las aplicaciones C#.
¿Qué características de C# complementan el uso de IronQR para el procesamiento de códigos QR?
Características de C# como async/await, Parallel.ForEach y SemaphoreSlim complementan a IronQR, proporcionando un marco para el procesamiento eficiente y de alto rendimiento de códigos QR.
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 estructurados y visualmente atractivos.