Como Usar Async e Multithreading para Operações de Código QR em C
A leitura de código QR de um único thread bloqueia o thread de chamada durante a duração de cada decodificação de imagem.
Em um manipulador de botão WPF, isso congela a interface do usuário até que a decodificação seja concluída. Em um processamento em lote de centenas de imagens, isso deixa núcleos da CPU ociosos quando poderiam estar trabalhando em paralelo. O método ReadAsync do IronQR descarrega leituras individuais para uma tarefa aguardável, e o método padrão Read funciona com Parallel.ForEach e Task.WhenAll para processamento em lote.
Este guia demonstra como processar códigos QR de forma assíncrona, distribuir leituras em lote entre os núcleos da CPU e combinar ambos os padrões para pipelines de alto volume.
Início rápido: Processar Códigos QR Assincronamente
Carrega uma imagem e aguarda os resultados decodificados sem bloquear a thread de chamada.
-
Instale IronQR com o Gerenciador de Pacotes NuGet
PM > Install-Package IronQR -
Copie e execute este trecho 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); -
Implante para testar em seu ambiente de produção.
Comece a usar IronQR em seu projeto hoje com uma avaliação gratuita
Fluxo de trabalho mínimo (5 etapas)
- Baixar a biblioteca C# IronQR para processamento assíncrono de códigos QR
- Use
ReadAsyncpara leituras únicas não bloqueantes. - Use
Parallel.ForEachpara processamento em lote vinculado à CPU - Combine com
SemaphoreSlimpara pipelines de concorrência limitados - Colete os resultados de
IEnumerablee imprima os valores decodificados.
Leitura assíncrona de códigos QR
ReadAsync retorna uma tarefa assíncrona, tornando-a compatível com manipuladores de eventos WPF/MAUI, ações de controladores ASP.NET ou qualquer método assíncrono. A entrada deve ser construída a partir de um bitmap de imagem; não há sobrecarga de caminho de arquivo.
O lado da escrita é síncrono e não possui variantes assíncronas. Para evitar o bloqueio da thread durante a E/S de arquivos, envolva a etapa de salvamento em File.WriteAllBytesAsync() usando os bytes brutos exportados do bitmap.
Entrada
Um crachá de evento com código QR foi escaneado e regenerado para demonstrar o padrão de leitura e gravação assí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)
Saída
O terminal exibe o tipo e o valor do QR decodificado no formato [QrType] Value e, em seguida, confirma que output-qr.png foi salvo.
QrScanMode.Auto executa tanto a detecção por aprendizado de máquina quanto uma varredura básica, preenchendo o valor decodificado e o tipo de QR Code em cada resultado. OnlyDetectionModel é mais rápido, mas retorna apenas as coordenadas da caixa delimitadora, deixando o campo de valor vazio. Use Auto sempre que o conteúdo codificado for necessário.
Processamento de códigos QR com multithreading
Para imagens que podem ser decodificadas independentemente, Parallel.ForEach distribui o trabalho entre os núcleos de CPU disponíveis. Uma instância QrReader separada por iteração é o padrão seguro, já que o IronQR não oferece nenhuma garantia explícita de segurança de thread para instâncias de leitor compartilhadas.
Entrada
Quatro das dez imagens de teste de código QR usadas na leitura em lote paralela. Cada imagem codifica um URL e é lida da pasta qr-images/ em tempo de execução.
Imagem 1 (Lote 1 de 10)
Imagem 2 (Lote 2 de 10)
Imagem 3 (Lote 3 de 10)
Imagem 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")
Saída
O console exibe um resumo do lote, incluindo o número de arquivos processados, o tempo de processamento, os códigos QR encontrados, quaisquer falhas e a taxa de transferência. Em seguida, lista cada nome de arquivo com seu URL decodificado.
Baixe todas as 10 imagens de teste para entrada de código QR em lote (batch-qr-images.zip).
ConcurrentBag<t> reúne resultados de todas as threads sem exigir bloqueios. Um contador thread-safe rastreia falhas, e o uso de try-catch para cada arquivo garante que uma imagem defeituosa não interrompa todo o lote. Essa abordagem segue o padrão de isolamento de erros descrito no guia de tratamento de erros .
Defina MaxDegreeOfParallelism para Environment.ProcessorCount para alinhar com o número de núcleos da CPU. O uso de threads adicionais aumenta a sobrecarga e não melhora o desempenho, especialmente para modelos de aprendizado de máquina que exigem muito da CPU.
Combinando processamento assíncrono e paralelo
Para pipelines de alto volume, combine SemaphoreSlim com Task.WhenAll para limitar a concorrência. Ao contrário de Parallel.ForEach, este padrão mantém a E/S não bloqueante, controlando ao mesmo tempo quantas decodificações são executadas simultaneamente, evitando a saturação do pool de threads sob grandes cargas de trabalho.
Entrada
Quatro das vinte imagens de teste de código QR processadas pelo pipeline concorrente. Cada imagem codifica um URL e é decodificada em paralelo usando concorrência limitada via SemaphoreSlim.
Imagem 1 (Tubulação 1 de 20)
Imagem 2 (Tubo 2 de 20)
Imagem 3 (Tubulação 3 de 20)
Imagem 4 (Tubulação 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
Saída
O console exibe um resumo quando o pipeline termina: total de códigos QR decodificados, número de arquivos de origem e tempo decorrido, seguido por cada nome de arquivo e seu URL decodificado.
Baixe todas as 20 imagens de entrada de código QR do pipeline de teste (high-volume-qr-images.zip).
Ajuste o limite de semáforos à quantidade de núcleos disponíveis para obter a melhor taxa de transferência ou reduza-o quando a pressão sobre a memória for uma preocupação com imagens grandes.
Leitura complementar
- Exemplo de varredura ML : comparação do modo de varredura com exemplos de código.
- Como ler códigos QR : estrutura de entrada e padrões básicos de leitura.
- Tutorial de Gerador de Código QR : geração com estilização.
- Referência da API do QrReader : assinaturas de métodos e observações.
- Referência da API QrWriter : todas as sobrecargas de escrita.
- Guia de tratamento de erros : isolamento de erros por arquivo e padrões de registro.
Visualizar opções de licenciamento quando o pipeline estiver pronto para produção.

