PLAN_01_リファクタリング計画.md の全フェーズが完了し,動作がリファクタリング前と同一であることを確認してから着手する.[fix] タグを使用する.
[fix] _shots へのスレッドセーフなアクセスに修正_shots へのスレッドセーフでないアクセス(根本原因)原因
_shots(List<Mat>)は Shot スレッドと SaveThread から同時にアクセスされる.
List<T> はスレッドセーフではないため,以下の2点で競合が発生する.
List<T> の内部配列は容量が足りなくなると自動で拡張される(デフォルト容量 4 → 5枚目の Add 時に 8 に拡張)._shots[saveCount] を読むとメモリ破壊が起きる._shots.Count チェックと _shots[saveCount] アクセスの間に Shot スレッドがリサイズを起こすと IndexOutOfRangeException が発生する.修正方針
_shots を List<Mat> から System.Collections.Concurrent.ConcurrentQueue<Mat> またはロック付きアクセスに変更する.SemaphoreSlim 等)に変更する.テスト
ConvertImage 内の Mat 未 Dispose によるメモリ蓄積原因
ConvertImage 内で生成される中間 Mat(extended,converted,convertedImage)が Dispose されないまま残る.
1回の呼び出しで数百 MB〜1 GB 超が蓄積し,画像枚数×チャンネル数分だけ積み重なる.
(例: IScam 1840×2440,17次元,4枚連続撮影 → 数十 GB 規模)
修正方針
extended,converted,convertedImage を using ブロックで管理して確実に Dispose する.テスト
Parallel.For 内での未捕捉例外原因
ExtendMat の Parallel.For 内で OOM 等の例外が発生した場合,AggregateException としてラップされて SaveThread に伝播する.
SaveThread で捕捉されなければアプリがクラッシュする.
※ 優先度2の修正でメモリ蓄積が解消されれば,発生頻度は大幅に下がる.
修正方針
ExtendMat の Parallel.For に try-catch を追加する.テスト
ConvertImage による _shots[i] の in-place 書き換え原因
ConvertImage 内の src.ConvertTo(src, CV_64FC3) が _shots[i] 自体を CV_8UC3 から CV_64FC3 に書き換える副作用がある.
特に IScam の new Mat(imgt_full, _roi)(サブマトリクス)に対して in-place の型変換を行うと不正な動作を引き起こす可能性がある.
また複数チャンネルを処理する際,2回目以降は変換済みのデータに対して処理をかけることになり,変換結果が誤る.
修正方針
ConvertImage の冒頭で src をコピーし,コピー側を変換するようにする(src を直接書き換えない).テスト
4,10,17)で撮影し,各チャンネルの sRGB 変換画像が正しく出力されることを確認する.原因 A: _shots.Clear() で Mat が解放されない
_shots.Clear() が呼ばれるが,格納された Mat は Dispose されない.原因 B: _chartMasks.Clear() で Mat が解放されない
_chartMasks.Clear() が呼ばれるが,同様に Dispose されない.修正方針
テスト
| 順位 | 原因 | クラッシュとの関連 |
|---|---|---|
| 1 | _shots のスレッドセーフでないアクセス | 直接原因(「たまに落ちる」に一致) |
| 2 | ConvertImage 内の Mat 未 Dispose | 直接原因(OOM によるクラッシュ) |
| 3 | Parallel.For 内の未捕捉例外 | 間接原因(2の修正で頻度減) |
| 4 | ConvertImage の in-place 書き換え | 誤動作(変換結果の誤り) |
| 5 | _shots / _chartMasks の Mat 未 Dispose | メモリリーク(蓄積) |