如何在 C# 中使用异步和多线程进行二维码操作

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

单线程二维码扫描会在每次图像解码期间阻塞调用线程。

在 WPF 按钮处理程序中,这将冻结 UI,直到解码完成。 在批量处理数百张图像时,CPU 核心会闲置,而它们本可以并行工作。 IronQR 的 ReadAsync 方法将单次读取操作卸载到可等待任务中,而标准的 Read 方法则配合 Parallel.ForEachTask.WhenAll 实现批量吞吐。

本指南演示了如何异步处理二维码、将批量读取任务分配到 CPU 内核上,以及如何将这两种模式结合起来以实现高容量流水线。

快速入门:异步处理二维码

加载图像并等待解码结果,而不阻塞调用线程。

  1. 使用 NuGet 包管理器安装 https://www.nuget.org/packages/IronQR

    PM > Install-Package IronQR
  2. 复制并运行这段代码。

    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. 部署到您的生产环境中进行测试

    通过免费试用立即在您的项目中开始使用IronQR

    arrow pointer

异步读取二维码

ReadAsync 返回一个可等待的任务,使其与 WPF/MAUI 事件处理程序、ASP.NET 控制器操作或任何异步方法兼容。 输入必须由图像位图构成;不支持文件路径重载。

写入端是同步的,没有异步变体。 为避免在文件 I/O 过程中阻塞线程,请使用从位图导出的原始字节,将保存步骤封装在 File.WriteAllBytesAsync() 中。

输入

扫描并重新生成二维码活动徽章,以演示异步读写模式。

二维码编码 https://ironsoftware.com/event-badge 用作异步读取输入
: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

输出

终端以 [QrType] Value 的格式显示解码后的 QR 类型和值,随后确认已保存 output-qr.png

终端输出显示 [QRCode] 解码值和 output-qr.png 保存确认

QrScanMode.Auto 同时运行机器学习检测和基础扫描流程,并在每个结果中填充解码值和QR类型。 OnlyDetectionModel 速度更快,但仅返回边界框坐标,导致值字段为空。 如需显示编码内容,请使用 Auto


使用多线程处理二维码

对于可独立解码的图像,Parallel.ForEach 会将任务分配给可用的 CPU 核心。 每次迭代使用独立的 QrReader 实例是安全的默认做法,因为 IronQR 并未对共享的读取器实例提供明确的线程安全保证。

输入

并行批量扫描中使用的十个二维码测试​​图像中有四个。 每张图片都编码了一个 URL,并在运行时从 qr-images/ 文件夹中读取。

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

输出

控制台显示批处理摘要,包括已处理的文件数、处理时间、找到的二维码、任何故障和吞吐量。 然后它会列出每个文件名及其解码后的 URL。

终端输出显示并行批处理结果:已处理 10 个文件,已找到二维码,存在失败,吞吐量为 [数量],每个文件名已解码 URL

下载全部 10 个测试批次的二维码输入图像(batch-qr-images.zip)。

ConcurrentBag<t> 可从所有线程中收集结果,且无需使用锁。 线程安全的计数器会跟踪故障,并且对每个文件使用 try-catch 可以确保一个错误的图像不会中断整个批次。 这种方法遵循错误处理指南中描述的错误隔离模式。

MaxDegreeOfParallelism 设置为 Environment.ProcessorCount,以匹配 CPU 核心数。 使用额外的线程会增加开销,并且不会提高性能,特别是对于 CPU 密集型机器学习模型而言。


结合异步和并行处理

对于高吞吐量的管道,请将 SemaphoreSlimTask.WhenAll 配合使用以限制并发。 与 Parallel.ForEach 不同,此模式在保持 I/O 非阻塞的同时,还能控制同时运行的解码任务数量,从而防止在高负载情况下线程池饱和。

输入

并发管道处理的二十张 QR 测试图像中的四张。每张图像都编码了一个 URL,并通过 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

输出

控制台会在管道完成后显示摘要:解码的二维码总数、源文件数量和经过的时间,以及每个文件名及其解码后的 URL。

终端输出显示管道结果:从 20 个文件中生成 20 个二维码,每个文件名都包含解码后的 URL。

下载全部 20 个测试流程 QR 码输入图像(high-volume-qr-images.zip)。

为了提高吞吐量,应使信号量限制与可用核心数相匹配;如果处理大型图像时内存压力较大,则应降低信号量限制。


进一步阅读

当管道准备就绪投入生产时,请查看许可选项

常见问题解答

什么是C#中异步QR码处理?

C#中的异步QR码处理允许您执行QR码操作而不会阻塞主线程,使用像IronQR的ReadAsync方法这样的功能来提升性能和响应性。

多线程如何提高QR码的处理效率?

多线程可以显著提高QR码的处理效率,通过允许多项操作并发进行,利用IronQR可促进更快的处理时间和改进的应用效率。

IronQR中的ReadAsync方法是什么?

IronQR中的ReadAsync方法使QR码的异步读取成为可能,允许您的C#应用程序处理QR码数据而不会延误其他任务。

Parallel.ForEach在QR码处理中有何帮助?

Parallel.ForEach允许通过将任务分配到多个线程同时处理多个QR码,这可以与IronQR一起有效利用以加快QR码操作速度。

SemaphoreSlim在QR码操作中起什么作用?

SemaphoreSlim用于限制并发任务的数量,帮助在使用IronQR进行QR码的并行处理中有效管理资源。

IronQR是否可用于在有界管道中处理QR码?

是的,IronQR可用于在有界管道中处理QR码,使用如SemaphoreSlim的构造来有效地控制并发和资源分配。

使用IronQR进行异步QR码操作有哪些好处?

使用IronQR进行异步QR码操作改善了应用响应性,减少了主线程的阻塞,并允许高效处理大量的QR码数据。

是否可以使用IronQR并行运行QR码操作?

是的,IronQR支持并行处理QR码操作,允许您在C#中利用多线程能力快速且高效地处理QR码。

IronQR如何增强C#应用程序中的QR码处理?

IronQR通过提供强大的异步和多线程能力来增强QR码处理,减少处理时间并提高C#应用程序的可扩展性。

哪些C#功能可以补充IronQR在QR码处理中的使用?

C#特性如async/await、Parallel.ForEach和SemaphoreSlim补充了IronQR,提供了高效高性能的QR码处理框架。

Curtis Chau
技术作家

Curtis Chau 拥有卡尔顿大学的计算机科学学士学位,专注于前端开发,精通 Node.js、TypeScript、JavaScript 和 React。他热衷于打造直观且美观的用户界面,喜欢使用现代框架并创建结构良好、视觉吸引力强的手册。

除了开发之外,Curtis 对物联网 (IoT) 有浓厚的兴趣,探索将硬件和软件集成的新方法。在空闲时间,他喜欢玩游戏和构建 Discord 机器人,将他对技术的热爱与创造力相结合。

准备开始了吗?
Nuget 下载 67,270 | 版本: 2026.5 just released
Still Scrolling Icon

还在滚动吗?

想快速获得证据? PM > Install-Package IronQR
运行示例 观看您的 URL 变成 QR 代码。