画像ファイルからBitmapオブジェクト取得するコードなのですが、ファイルの読み込みとデコードを別スレッドで行いたいと思い作りました。
スレッドセーフなキューに画像ファイルのパスと、結果を処理するコールバックのコードを渡すと、処理されるようなコードになっています。
ファイル名:ImageProcessor.cs
using System.Collections.Concurrent;
using System.Diagnostics;
class ImageProcessor: IDisposable
{
private readonly BlockingCollection<(string path, Func<Bitmap, Task> callback)> _fileQueue = new();
private readonly BlockingCollection<(MemoryStream ms, Func<Bitmap, Task> callback)> _imageQueue = new();
private readonly CancellationTokenSource _cts = new();
private readonly List<Task> _workers = [];
public void EnqueueFile(string path, Func<Bitmap, Task> callback)
=> _fileQueue.Add((path, callback));
public void Start()
{
_workers.Add(Task.Run(() => ReaderThread(_cts.Token)));
for (int i = 0; i < Environment.ProcessorCount; i++)
{
_workers.Add(Task.Run(() => DecodeThread(_cts.Token)));
}
}
public void Stop()
{
_fileQueue.CompleteAdding();
_imageQueue.CompleteAdding();
_cts.Cancel();
}
public void Dispose()
{
Stop();
Task.WaitAll(_workers.ToArray(), TimeSpan.FromSeconds(5));
_cts.Dispose();
_fileQueue.Dispose();
_imageQueue.Dispose();
}
private async Task ReaderThread(CancellationToken token)
{
foreach (var (path, callback) in _fileQueue.GetConsumingEnumerable(token))
{
try
{
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
// zip対応予定
var ms = new MemoryStream();
await fs.CopyToAsync(ms, token);
ms.Seek(0, SeekOrigin.Begin);
_imageQueue.Add((ms, callback), token);
}
catch (Exception ex)
{
Debug.Print($"[Read] {path} => {ex.Message}");
throw;
}
}
}
private void DecodeThread(CancellationToken token)
{
try
{
foreach (var (ms, callback) in _imageQueue.GetConsumingEnumerable(token))
{
try
{
using var clone = new MemoryStream(ms.ToArray());
using var bmp = new Bitmap(clone);
callback(bmp).Wait(token);
}
catch (Exception ex)
{
Debug.Print($"[Decode] {ex.Message}");
throw;
}
finally
{
ms.Dispose();
}
}
}
catch (OperationCanceledException)
{
// 正常終了(ログなしでもOK)
}
}
}
ファイル名:Form1.cs
namespace GraphicUtilitesLib;
public partial class Form1 : Form
{
// イメージプロセッサ
ImageProcessor _imageProcessor = new();
PictureBox picbox1 = new()
{
Dock = DockStyle.Fill,
SizeMode = PictureBoxSizeMode.Zoom,
};
// コンストラクタ
public Form1()
{
InitializeComponent();
this.Controls.Add(picbox1);
_imageProcessor.Start();
}
// フォームロード
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_imageProcessor.EnqueueFile(@"C:\Users\karet\Pictures\2025-06-28105457.png", async bmp =>
{
this.Invoke(() =>
{
picbox1.Image?.Dispose(); // 古い画像を破棄(メモリリーク防止)
picbox1.Image = (Bitmap)bmp.Clone(); // Cloneして所有権を移す
});
await Task.Delay(10);
});
}
// フォームクローズド
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
_imageProcessor.Dispose();
}
}
ここまでやっても、速くなるわけでも無いのがC#の辛いところですが、GUIなどで非同期処理で使えるかと思います。
コメント