如何在 C# 中使用异步和多线程进行二维码操作
单线程二维码扫描会在每次图像解码期间阻塞调用线程。
在 WPF 按钮处理程序中,这将冻结 UI,直到解码完成。 在批量处理数百张图像时,CPU 核心会闲置,而它们本可以并行工作。 IronQR 的 ReadAsync 方法将单个读取操作卸载到可等待的任务中,而标准的 Read 方法与 Parallel.ForEach 和 Task.WhenAll 配合使用,以实现批量吞吐量。
本指南演示了如何异步处理二维码、将批量读取任务分配到 CPU 内核上,以及如何将这两种模式结合起来以实现高容量流水线。
快速入门:异步处理二维码
加载图像并等待解码结果,而不阻塞调用线程。
-
使用 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# 库,实现异步二维码处理
- 使用
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 参考:所有写入重载。 -错误处理方法:按文件进行错误隔离和日志记录模式。
当管道准备就绪投入生产时,请查看许可选项。

