如何在 C# 中使用 Async 和多執行緒進行 QR code 掃描與處理
單執行緒 QR 碼掃描會在每次影像解碼期間阻塞呼叫執行緒。
在 WPF 按鈕處理程序中,這將凍結 UI,直到解碼完成。 在批量處理數百張圖像時,CPU 核心會閒置,而它們本可以並行工作。 IronQR 的 ReadAsync 方法將單一讀取操作卸載到可等待的任務中,而標準的 Read 方法與 Parallel.ForEach 和 Task.WhenAll 使用,以實現批量匹配。
本指南示範如何非同步處理二維碼、將批次讀取任務分配到 CPU 核心上,以及如何將這兩種模式結合起來以實現高容量管線。
快速入門:異步處理 QR 碼
載入圖像並等待解碼結果,而不阻塞呼叫線程。
-
使用NuGet套件管理器安裝https://www.nuget.org/packages/IronQR
PM > Install-Package IronQR -
複製並運行這段程式碼。
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); -
部署到您的生產環境進行測試
今天就在您的專案中開始使用免費試用IronQR
最小工作流程(5 個步驟)
- 下載 IronQR C# 程式庫,以進行非同步 QR 碼處理
- 使用
ReadAsync進行非阻塞式單次讀取 - 使用
Parallel.ForEach進行 CPU 密集型批次處理 - 結合
SemaphoreSlim實現有限並發管道 - 從
IEnumerable中收集結果並列印解碼後的值
非同步讀取二維碼
ReadAsync 傳回一個可等待的任務,使其與 WPF/MAUI 事件處理程序、 ASP.NET控制器操作或任何非同步方法相容。 輸入必須由影像點陣圖構成;不支援檔案路徑重載。
寫入端是同步的,沒有非同步變體。 為了避免在檔案 I/O 期間阻塞線程,請使用從點陣圖匯出的原始位元組將保存步驟包裝在 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);
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)
輸出
終端以 [QrType] Value 的格式顯示解碼後的二維碼類型和值,然後確認 output-qr.png 已儲存。
QrScanMode.Auto 執行 ML 偵測和基本掃描過程,並在每個結果中填入解碼值和 QR 類型。 OnlyDetectionModel 速度較快,但僅傳回邊界框座標,值欄位為空。 需要編碼內容時,請使用 Auto。
使用多線程處理二維碼
對於可以獨立解碼的圖像,Parallel.ForEach 將工作分配到可用的 CPU 核心。 每次迭代使用單獨的 QrReader 實例是安全的預設做法,因為IronQR沒有對共享的讀取器實例做出明確的線程安全保證。
輸入
並行批次掃描中使用的十個二維碼測試影像中有四個。 每張圖片都編碼了一個 URL,並在運行時從 qr-images/ 資料夾中讀取。
圖 1(第 1 批,共 10 批)
圖 2(第 2 批,共 10 張)
圖 3(第 3 批,共 10 批)
圖 4(共 10 張,第 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")
輸出
控制台顯示批次摘要,包括已處理的檔案數、處理時間、找到的二維碼、任何故障和吞吐量。 然後它會列出每個檔案名稱及其解碼後的 URL。
下載全部 10 個測試批次的二維碼輸入影像(batch-qr-images.zip)。
ConcurrentBag<t> 從所有執行緒收集結果,而無需加鎖。 線程安全的計數器會追蹤故障,並且對每個檔案使用 try-catch 可以確保一個錯誤的映像不會中斷整個批次。 這種方法遵循錯誤處理指南中所述的錯誤隔離模式。
將 MaxDegreeOfParallelism 設定為 Environment.ProcessorCount,以與 CPU 核心數保持一致。 使用額外的執行緒會增加開銷,並且不會提高效能,特別是對於 CPU 密集型機器學習模型而言。
結合異步和平行處理
對於高容量管道,將 SemaphoreSlim 與 Task.WhenAll 配對,以限制並發性。 與 Parallel.ForEach 不同,這種模式保持 I/O 非阻塞,同時控制一次運行的解碼數量,防止在大工作負載下線程池飽和。
輸入
並發管線處理的二十個二維碼測試影像中的四個。每個圖像編碼一個 URL,並透過 SemaphoreSlim 使用有界並發並行解碼。
圖 1(共 20 道流程中的第 1 道)
圖 2(20 個流程中的第 2 個)
圖 3(共 20 道流程中的第 3 道)
圖 4(共 20 道流程中的第 4 道)
: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
輸出
控制台會在管道完成後顯示摘要:解碼的二維碼總數、來源檔案數量和經過的時間,以及每個檔案名稱及其解碼後的 URL。
下載全部 20 個測試流程 QR 碼輸入影像(high-volume-qr-images.zip)。
為了提高吞吐量,應使信號量限制與可用核心數相符;如果處理大型影像時記憶體壓力較大,則應降低信號量限制。
延伸閱讀
- ML 掃描範例:掃描模式與程式碼範例的比較。 -如何讀取二維碼:輸入結構與基本讀取模式。 -二維碼產生器教學:產生帶有樣式的二維碼。
- QrReader API 參考:方法簽章與備註。
- QrWriter API 參考:所有寫入重載。 -錯誤處理方法:按檔案進行錯誤隔離和日誌記錄模式。
當管道準備就緒可投入生產時,請查看授權選項。

