Jak używać asynchroniczności i wielowątkowości do operacji z kodami QR w języku C
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.
-
Install IronQR with NuGet Package Manager
PM > Install-Package IronQR -
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); -
Wdrożenie do testowania w środowisku produkcyjnym
Rozpocznij używanie IronQR w swoim projekcie już dziś z darmową wersją próbną
Minimalny proces (5 kroków)
- Pobierz bibliotekę IronQR C# do asynchronicznego przetwarzania kodów QR
- Użyj
ReadAsyncdo nieblokujących odczytów pojedynczych - Użyj
Parallel.ForEachdo przetwarzania wsadowego obciążającego procesor - Połącz z
SemaphoreSlim,aby uzyskać potoki o ograniczonej współbieżności - Zbierz wyniki z
IEnumerablei wydrukuj zdekodowane wartości
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.
: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)
Wynik
Terminal wyświetla zdekodowany typ i wartość QR w formacie [QrType] Value, a następnie potwierdza, że output-qr.png został zapisany.
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.
Zdjęcie 1 (partia 1 z 10)
Zdjęcie 2 (partia 2 z 10)
Zdjęcie 3 (partia 3 z 10)
Zdjęcie 4 (partia 4 z 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")
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.
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.
Zdjęcie 1 (Potok 1 z 20)
Zdjęcie 2 (Pipeline 2 z 20)
Zdjęcie 3 (Pipeline 3 z 20)
Zdjęcie 4 (Pipeline 4 z 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
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.
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
- Przykład skanowania ML: porównanie trybów skanowania z przykładami kodu.
- Instrukcja odczytu kodów QR: budowa wejścia i podstawowe wzorce odczytu.
- Samouczek dotyczący generatora kodów QR: generowanie z zastosowaniem stylów.
- Dokumentacja API QrReader: sygnatury metod i uwagi.
- Dokumentacja API QrWriter: wszystkie nadładowania funkcji write.
- Poradnik obsługi błędów: izolacja błędów na poziomie plików i wzorce rejestrowania.
Zapoznaj się z opcjami licencyjnymi, gdy potok będzie gotowy do produkcji.

