qrコード 読み取りで非同期とマルチスレッドをC#で使用する方法
単一スレッドのQRスキャンは、すべての画像デコードの間、呼び出しスレッドをブロックします。
WPFのボタンハンドラーでは、デコードが完了するまでUIがフリーズします。 数百枚の画像を処理するバッチ処理では、並列処理が可能なCPUコアがアイドル状態になってしまう。 IronQR の ReadAsync メソッドは個々の読み取りを待機可能なタスクにオフロードし、標準の Read メソッドはバッチ処理のスループットのために Parallel.ForEach および Task.WhenAll と連携します。
このガイドでは、QRコードを非同期で処理する方法、バッチ読み取りをCPUコア全体に分散させる方法、および両方のパターンを組み合わせて大容量パイプラインを構築する方法について説明します。
クイックスタート: QRコードを非同期で処理する
画像を読み込み、呼び出し元のスレッドをブロックせずにデコード結果を待機します。
-
IronQR をNuGetパッケージマネージャでインストール
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ステップ)
- 非同期QRコード処理のためのIronQR C#ライブラリをダウンロード
- 非ブロッキングの単一読み取りには
ReadAsync使用してください。 Parallel.ForEachを使用してCPUバウンドバッチ処理を行うSemaphoreSlimと組み合わせて制限付き同時実行パイプラインを作成するIEnumerableから結果を取得し、デコードされた値を表示します。
QRコードを非同期で読み取る
ReadAsync は待機可能なタスクを返すため、WPF/MAUI イベント ハンドラー、 ASP.NETコントローラー アクション、または任意の非同期メソッドと互換性があります。 入力は画像ビットマップから構築する必要があり、ファイルパスによる指定はできません。
書き込み側は同期処理であり、非同期処理のバリエーションはありません。 ファイルI/O中にスレッドがブロックされないようにするには、ビットマップからエクスポートされた生のバイトを使用して、保存ステップをFile.WriteAllBytesAsync()で囲みます。
入力
非同期読み書きパターンを実証するために、QRコードイベントバッジをスキャンして再生成した。
: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)
出力
端末は、デコードされたQRコードの種類と値をoutput-qr.pngが保存されたことを確認します。
QrScanMode.Auto は、機械学習検出と基本的なスキャンパスの両方を実行し、各結果にデコードされた値と QR タイプを設定します。 OnlyDetectionModel は高速ですが、バウンディングボックスの座標のみを返し、値フィールドは空になります。 エンコードされたコンテンツが必要な場合は、いつでも Auto を使用してください。
マルチスレッドによるQRコードの処理
独立してデコードできる画像の場合、Parallel.ForEach は利用可能な CPU コアに処理を分散します。 IronQR は共有リーダー インスタンスに対して明示的なスレッド セーフティを保証しないため、反復処理ごとに個別の QrReader インスタンスを使用することが安全なデフォルトです。
入力
並列バッチスキャンで使用された10個のQRコードテスト画像のうち4個。 各画像はURLをエンコードしており、実行時にqr-images/フォルダから読み込まれます。
画像1(全10枚中1枚目)
画像2(全10枚中2枚目)
画像3(全10枚中3枚目)
画像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")
出力
コンソールには、処理されたファイル数、処理時間、検出されたQRコード数、発生したエラー、スループットなどを含むバッチの概要が表示されます。 次に、各ファイル名とそのデコードされたURLを一覧表示します。
テストバッチ用のQRコード入力画像10枚すべてをダウンロードしてください(batch-qr-images.zip)。
ConcurrentBag<t> は、ロックを必要とせずにすべてのスレッドから結果を収集します。 スレッドセーフなカウンターが障害を追跡し、各ファイルにtry-catchブロックを使用することで、1つの不良画像がバッチ処理全体を中断させないようにしています。 このアプローチは、エラー処理のハウツーで説明されているエラー分離パターンに従います。
CPUコア数に合わせて、MaxDegreeOfParallelism を Environment.ProcessorCount に設定してください。 スレッド数を増やすとオーバーヘッドが増加するだけで、特にCPU負荷の高い機械学習モデルではパフォーマンスが向上しません。
非同期処理と並列処理の組み合わせ
大量のパイプラインの場合、SemaphoreSlim と Task.WhenAll を組み合わせて、同時実行数を制限します。 Parallel.ForEachとは異なり、このパターンはI/Oをノンブロッキングに保ちながら、同時に実行されるデコードの数を制御することで、大規模なワークロード下でのスレッドプールの飽和を防ぎます。
入力
並列パイプラインで処理された 20 個の QR コード テスト画像のうち 4 個。各画像は URL をエンコードしており、SemaphoreSlim を介して制限付き並列処理を使用して並列にデコードされます。
画像1(パイプライン1/20)
画像2(パイプライン2/20)
画像3(パイプライン3/20)
画像4(パイプライン4/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
出力
パイプラインが完了すると、コンソールに概要が表示されます。表示されるのは、デコードされたQRコードの総数、ソースファイルの数、経過時間、そして各ファイル名とそのデコードされたURLです。
テストパイプラインのQRコード入力画像20枚すべてをダウンロードしてください(high-volume-qr-images.zip)。
スループットを最大化するために、セマフォの制限値を利用可能なコア数に合わせるか、大きな画像でメモリ負荷が懸念される場合は、制限値を下げてください。
さらなる読み物
- MLスキャン例:コードサンプルを用いたスキャンモードの比較。
- QRコードの読み取り方法:入力構造と基本的な読み取りパターン。
- QRコード生成チュートリアル:スタイル設定付き生成。
- QrReader APIリファレンス:メソッドのシグネチャと注釈。
- QrWriter API リファレンス:すべての書き込みオーバーロード。 -エラー処理方法:ファイルごとのエラー分離とログ記録パターン。

