Jak używać asynchroniczności i wielowątkowości do operacji z kodami QR w języku C

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

Skanowanie kodów QR w trybie jednowątkowym blokuje wątek wywołujący na czas dekodowania każdego obrazu.

W procedurze obsługi przycisku WPF powoduje to zawieszenie interfejsu użytkownika do czasu zakończenia dekodowania. W zadaniu wsadowym przetwarzającym setki obrazów pozostawia rdzenie procesora bezczynne, podczas gdy mogłyby one pracować równolegle. Metoda ReadAsync w IronQR przenosi indywidualne odczyty do zadania oczekującego, a standardowa metoda Read działa z Parallel.ForEach i Task.WhenAll dla przetwarzania wsadowego.

W niniejszym przewodniku pokazano, jak przetwarzać kody QR asynchronicznie, rozdzielać odczyty partii między rdzenie procesora oraz łączyć oba wzorce w przypadku potoków o dużej przepustowości.

Szybki start: Asynchroniczne przetwarzanie kodów QR

Załaduj obraz i poczekaj na zdekodowane wyniki bez blokowania wątku wywołującego.

  1. Install IronQR with NuGet Package Manager

    PM > Install-Package IronQR
  2. Skopiuj i uruchom ten fragment kodu.

    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. Wdrożenie do testowania w środowisku produkcyjnym

    Rozpocznij używanie IronQR w swoim projekcie już dziś z darmową wersją próbną

    arrow pointer

Asynchroniczne odczytywanie kodów QR

ReadAsync zwraca zadanie oczekujące, co sprawia, że jest kompatybilne z handlerami zdarzeń WPF/MAUI, akcjami kontrolerów ASP.NET lub dowolną metodą asynchroniczną. Dane wejściowe muszą być utworzone z bitmapy obrazu; nie ma przeciążenia ścieżki pliku.

Strona pisania jest synchroniczna i nie ma wariantów asynchronicznych. Aby uniknąć blokowania wątku podczas operacji I/O na plikach, opakuj krok zapisywania w File.WriteAllBytesAsync(), używając surowych bajtów eksportowanych z bitmapy.

Dane wejściowe

Identyfikator wydarzenia w postaci kodu QR zeskanowany i wygenerowany ponownie w celu zademonstrowania asynchronicznego wzorca odczytu i zapisu.

Kodowanie kodu QR https://ironsoftware.com/event-badge używane jako asynchroniczne odczytywanie danych wejściowych
: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)
$vbLabelText   $csharpLabel

Wynik

Terminal wyświetla zdekodowany typ i wartość QR w formacie [QrType] Value, a następnie potwierdza, że output-qr.png został zapisany.

Wynik na terminalu pokazujący zdekodowaną wartość [QR] oraz potwierdzenie zapisania pliku output-qr.png

QrScanMode.Auto uruchamia zarówno detekcję ML jak i podstawowe skanowanie, wypełniając zdekodowaną wartość i typ QR w każdym wyniku. OnlyDetectionModel jest szybszy, ale zwraca tylko współrzędne ramki ograniczającej, pozostawiając pole wartości puste. Użyj Auto za każdym razem, gdy potrzebna jest zakodowana zawartość.


Przetwarzanie kodów QR z wykorzystaniem wielowątkowości

Dla obrazów, które mogą być zdekodowane niezależnie, Parallel.ForEach rozdziela pracę na dostępne rdzenie CPU. Oddzielna instancja QrReader na każdą iterację jest bezpiecznym domyślnym ustawieniem, ponieważ IronQR nie gwarantuje w sposób jasny bezpieczeństwa wątków dla współdzielonych instancji czytników.

Dane wejściowe

Cztery z dziesięciu obrazów testowych kodów QR użytych w równoległym skanowaniu partii. Każdy obraz koduje URL i jest odczytywany z folderu qr-images/ podczas uruchamiania.

Batch QR code input image 1 of 10 encoding https://ironsoftware.com/batch-1
Batch QR code input image 2 of 10 encoding https://ironsoftware.com/batch-2
Batch QR code input image 3 of 10 encoding https://ironsoftware.com/batch-3
Batch QR code input image 4 of 10 encoding https://ironsoftware.com/batch-4
: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")
$vbLabelText   $csharpLabel

Wynik

Konsola wyświetla podsumowanie partii, w tym liczbę przetworzonych plików, czas przetwarzania, znalezione kody QR, ewentualne błędy oraz przepustowość. Następnie podaje każdą nazwę pliku wraz z jego zdekodowanym adresem URL.

Wynik terminala pokazujący równoległe wyniki partii: 10 przetworzonych plików, znalezione kody QR, błędy, przepustowość i zdekodowany adres URL na nazwę pliku

Pobierz wszystkie 10 obrazów testowych do wprowadzania kodów QR (batch-qr-images.zip).

ConcurrentBag<t> zbiera wyniki z wszystkich wątków bez potrzeby użycia blokad. Licznik bezpieczny dla wątków śledzi awarie, a użycie try-catch dla każdego pliku gwarantuje, że jeden uszkodzony obraz nie przerwie całej partii. Podejście to jest zgodne z wzorcem izolacji błędów opisanym w poradniku dotyczącym obsługi błędów.

Ustaw MaxDegreeOfParallelism na Environment.ProcessorCount, aby dopasować się do liczby rdzeni CPU. Użycie dodatkowych wątków zwiększa obciążenie i nie poprawia wydajności, szczególnie w przypadku modeli ML wymagających dużej mocy obliczeniowej procesora.


Łączenie przetwarzania asynchronicznego i równoległego

Dla potoków o dużej przepustowości, połącz SemaphoreSlim z Task.WhenAll, aby ograniczyć współbieżność. W przeciwieństwie do Parallel.ForEach, ten wzorzec utrzymuje nieblokujące I/O, jednocześnie kontrolując, ile dekodacji jest wykonywanych jednocześnie, co zapobiega przeciążeniu puli wątków przy dużym obciążeniu.

Dane wejściowe

Cztery z dwudziestu obrazów testowych kodów QR przetworzonych przez współbieżny potok. Każdy obraz koduje URL i jest dekodowany równolegle przy użyciu ograniczonej współbieżności za pomocą SemaphoreSlim.

Pipeline QR code input image 1 of 20
Pipeline QR code input image 2 of 20
Pipeline QR code input image 3 of 20
Pipeline QR code input image 4 of 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
$vbLabelText   $csharpLabel

Wynik

Po zakończeniu działania potoku konsola wyświetla podsumowanie: łączną liczbę zdekodowanych kodów QR, liczbę plików źródłowych i czas, który upłynął, a następnie nazwę każdego pliku i jego zdekodowany adres URL.

Wynik działania programu wyświetlany w terminalu: 20 kodów QR z 20 plików z odkodowanym adresem URL dla każdego pliku

Pobierz wszystkie 20 obrazów testowych z kodami QR (high-volume-qr-images.zip).

Dostosuj limit semaforów do dostępnej liczby rdzeni w celu uzyskania przepustowości lub zmniejsz go, gdy obciążenie pamięci stanowi problem w przypadku dużych obrazów.


Więcej informacji

Zapoznaj się z opcjami licencyjnymi, gdy potok będzie gotowy do produkcji.

Curtis Chau
Autor tekstów technicznych

Curtis Chau posiada tytuł licencjata z informatyki (Uniwersytet Carleton) i specjalizuje się w front-endowym rozwoju, z ekspertką w Node.js, TypeScript, JavaScript i React. Pasjonuje się tworzeniem intuicyjnych i estetycznie przyjemnych interfejsów użytkownika, Curtis cieszy się pracą z nowoczesnymi frameworkami i tworzeniem dobrze zorganizowanych, atrakcyjnych wizualnie podrę...

Czytaj więcej
Gotowy, aby rozpocząć?
Nuget Pliki do pobrania 63,676 | Wersja: 2026.4 just released
Still Scrolling Icon

Wciąż przewijasz?

Czy chcesz szybko dowodu? PM > Install-Package IronQR
uruchom próbkę obserwuj, jak Twój URL staje się kodem QR.