動的配列がロックアウトされている
前述のコードは十数分で破棄されました。インデックスのエラーを解消したので、関数全体は正常に稼動するかに思われましたが、今度は配列のロックアウトエラーというものが発生しました。
VB の配列は OLE の SafeArray として実装されていることは事前に知っていたので、エラーメッセージとヘルプによって何が発生しているかはすぐにわかりました。
SafeArray は2箇所以上から内容を更新されることを防ぐためのロック機能があり、サブルーチン A が操作している配列をサブルーチン B が操作しようとしていて、ロックアウトすることはできないというエラーですが、この場合は親要素を追加している AddElement が ary() の使用を完了していない間、子要素を追加している AddElement は ary() を ReDim できないということになります。
これは、ちょっと困ったことです。再帰構造内から動的配列の大きさを変更できないことになります。
日記を記述しつつ考え直すと、VB が裏で勝手にロックした配列をいつ開放するかの問題でしかなく、パフォーマンスをちょっと犠牲にしてスコープが変化するごとに配列のロックを開放するなどすればよいだけであり、最適化が強すぎるだけの結果に思えましたが、この問題を取り組んでいるときには思いつきませんでした。
また、そのような配列操作の制御を変更する最適化オプションが存在するかどうかは不明です。
これを簡単に回避するには、再帰関数の中で配列の要素数を変更するのをやめればよいことになります。そこで、事前に要素追加の条件をすべて確認し、最終的にいくつの要素を追加することになるのかを計算することにしました。
Private Function CountElement(ElementData) CountElements = CntElement(ElementData) End Function Private Function CntElement(ElementData) CntElement = 1 If ElementData.hasChild Then CntElement = CntElement + CntElement(ElementData.child) End If End Function Private Sub AddElement(ary() as MyType, ElementData) ary( Index ).member = ... If ElementData.hasChild Then AddElement(ElementData.Child) End If End Sub : ReDim ary(0 To CountElement(ElementData)) AddElement(ary(), ElementData) :
少し複雑化しましたが、こんな感じです。
この変更によって ReDim は再帰関数の外で最初に1回だけ実行することになり、要素を追加するごとに ReDim を行っていたのと比較して処理速度の高速化が体感できたほか、前述のエラーハンドリングによるインデックスエラーの回避策が必要なくなりました。ついでに CountElement 側で ElementData に矛盾した設定がないかのエラーチェックなどを取り入れました。