Como Usar Async e Multithreading para Operações de Código QR em C#

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

A digitalização de 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 que congela a interface do usuário, ou um trabalho em lote que processa 500 imagens sequencialmente quando 8 núcleos de CPU estão ociosos, a solução é a mesma: mover a decodificação para fora do thread de chamada. IronQR fornece ReadAsync para leituras únicas não bloqueantes e um método padrão Read que se integra naturalmente com Parallel.ForEach e Task.WhenAll para processamento em lote.

Abaixo, cobrimos leitura assíncrona, processamento em lote paralelo e um padrão combinado com concorrência controlada.

Início rápido: Processar Códigos QR Assincronamente

Carregue uma imagem, passe para ReadAsync, e aguarde os resultados decodificados sem bloquear o thread chamador.

  1. Instale IronQR com o Gerenciador de Pacotes NuGet

    PM > Install-Package IronQR
  2. 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);
  3. Implante para testar em seu ambiente de produção.

    Comece a usar IronQR em seu projeto hoje com uma avaliação gratuita

    arrow pointer

Como Ler Códigos QR Assincronamente?

QrReader.ReadAsync(IQrInput) retorna Task<IEnumerable<QrResult>>, tornando-o diretamente aguardável em contextos assíncronos — manipuladores de eventos WPF/MAUI, ações de controlador ASP.NET, ou qualquer método async Task. A entrada deve ser construída como um QrImageInput de um AnyBitmap; não há sobrecarga de caminho de arquivo.

O lado de gravação é diferente: QrWriter.Write() é síncrono, e QrCode.Save() retorna AnyBitmap sincronamente — o IronQR não expõe um método SaveAsync. Para evitar o bloqueio na parte de E/S do arquivo de um fluxo de trabalho de geração e salvamento, envolvemos a etapa de salvamento com File.WriteAllBytesAsync().

: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);
$vbLabelText   $csharpLabel

O parâmetro QrScanMode.OnlyDetectionModel informa ao leitor para usar exclusivamente o modelo de detecção ML, que é mais rápido por imagem do que QrScanMode.Auto (ML + varredura básica). Para leituras de imagem única em um contexto de UI, a diferença de velocidade é pequena; para lotes, ela se acumula significativamente.

Observe o comentário explícito sobre a etapa de salvamento: File.WriteAllBytesAsync() é padrão do .NET, não um método IronQR. Destacamos isso porque misturar o Save() síncrono do IronQR com um await na parte de E/S é uma fonte comum de confusão. O método ExportBytes() em AnyBitmap fornece a matriz de bytes PNG bruta para a gravação assíncrona.


Como Processar Códigos QR com Multithreading?

A varredura em lote se beneficia de Parallel.ForEach quando cada imagem pode ser processada de forma independente. Criamos uma nova instância QrReader por iteração como padrão seguro para threads — a documentação do IronQR não fornece uma garantia explícita de segurança para leitor compartilhado, então a construção por thread é a escolha conservadora.

: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");
$vbLabelText   $csharpLabel

O ConcurrentBag<t> coleta resultados entre threads sem bloqueio. Interlocked.Increment conta falhas com segurança. O bloco try-catch por arquivo garante que uma imagem corrompida não aborte todo o lote — o mesmo padrão de isolamento de erro descrito no como lidar com erros.

Definir MaxDegreeOfParallelism para Environment.ProcessorCount limita a criação de threads aos núcleos disponíveis. Inscrever-se além disso adiciona sobrecarga de troca de contexto sem melhorar o throughput, particularmente para o modelo ML que é vinculado à CPU.

O QrScanMode passado para QrImageInput controla o custo por imagem e se acumula pelo lote. OnlyDetectionModel executa o modelo ML sozinho — mais rápido para códigos impressos limpos. Auto executa tanto a detecção ML quanto uma passagem de varredura básica, trocando velocidade por precisão em códigos danificados ou enviesados. OnlyBasicScan pula o ML completamente e usa apenas o decodificador tradicional. Para o processamento em lote de etiquetas impressas por máquina onde a qualidade da imagem é consistente, OnlyDetectionModel fornece o melhor rendimento. Para a digitalização de documentos onde os códigos QR podem estar parcialmente ocultos, Auto é a escolha mais segura ao custo de aproximadamente 2× por imagem.


Como Combinar Processamento Assíncrono e Paralelo?

O padrão de produção para pipelines de alto volume usa SemaphoreSlim para limitar a concorrência ao executar ReadAsync dentro de um fan-out Task.WhenAll. Isso nos dá E/S não bloqueante (carregamento de imagem) combinado com paralelismo de CPU controlado (decodificação ML), sem sobrecarregar o pool de threads.

: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");
$vbLabelText   $csharpLabel

A escolha QrScanMode importa em escala. OnlyDetectionModel executa o modelo de detecção ML sozinho, otimizado para velocidade — ideal para quadros de câmeras e processamento em lote. Auto combina ML com uma passagem de varredura básica para maior precisão em códigos danificados, a um custo de aproximadamente 2× por imagem. OnlyBasicScan pula totalmente o ML e usa apenas o decodificador tradicional. Para um pipeline de produção processando etiquetas QR impressas limpas, OnlyDetectionModel fornece o melhor equilíbrio entre rendimento e precisão.


Quais são os meus próximos passos?

Cobrimos ReadAsync para leituras de QR não bloqueantes, Parallel.ForEach para rendimento em lote vinculado à CPU, e SemaphoreSlim + Task.WhenAll para paralelismo assíncrono limitado. O enum QrScanMode controla o equilíbrio entre velocidade e precisão no nível por imagem, e OnlyDetectionModel é o padrão correto para pipelines de alto volume.

Para leitura adicional:

Visualizar opções de licenciamento quando o pipeline estiver pronto para produção.

Curtis Chau
Redator Técnico

Curtis Chau é bacharel em Ciência da Computação (Universidade Carleton) e se especializa em desenvolvimento front-end, com experiência em Node.js, TypeScript, JavaScript e React. Apaixonado por criar interfaces de usuário intuitivas e esteticamente agradáveis, Curtis gosta de trabalhar com frameworks modernos e criar manuais ...

Leia mais
Pronto para começar?
Nuget Downloads 61,359 | Versão: 2026.3 acaba de ser lançado
Still Scrolling Icon

Ainda está rolando a tela?

Quer provas rápidas? PM > Install-Package IronQR
executar um exemplo Veja seu URL se transformar em um código QR.