【キュー構造活用】System.Collections.Queue を利用した FIFO(先入れ先出し)型タスク処理パイプラインの構築
現場のエンジニア諸君、日々の業務自動化でお疲れ様だ。
VBScriptと聞くと、「古い」「モダンではない」「エラーハンドリングが貧弱だ」と侮る者も多い。だが、レガシーなWindows環境において、追加のランタイムをインストールすることなく、OSの標準機能だけでミリ秒単位の制御を完結させられるVBScriptは、依然としてインフラ・バックオフィス自動化の最強の武器である。
特に、大量のファイル群のバッチ処理、APIリクエストの順次送信、あるいはDBレコードの段階的処理といった「順番が命」のタスクにおいて、配列(Array)の要素を無理やり `Redim Preserve` で拡張・削除していく非効率な実装に辟易していないだろうか?
今回は、COMの相互運用性を極限まで高め、.NETの強力なコレクションクラスである `System.Collections.Queue` をVBScriptのメモリ空間に召喚する。FIFO(First-In, First-Out:先入れ先出し)の原則に基づいた、堅牢かつ高速なタスク処理パイプラインの構築手法を伝授しよう。
—
1. なぜ「配列の再定義(ReDim)」は地獄なのか?
多くのVBScript初学者、あるいは我流で書いてきたプログラマーは、タスクリストを処理する際に以下のようなコードを書く。
‘ 【アンチパターン】配列の都度リサイズ
Dim tasks()
ReDim tasks(0)
tasks(0) = “task1”
‘ 要素を追加するたびに…
ReDim Preserve tasks(UBound(tasks) + 1)
tasks(UBound(tasks)) = “task2”
このアプローチは、小規模なデータであれば動く。だが、処理対象が1,000件、10,000件と増えた瞬間、O(N^2)のメモリ再割り当てコストが発生し、スクリプトの実行速度は急激に劣化する。さらに、先頭のデータを処理した後に「配列の先頭を切り詰める(シフトする)」処理を書こうものなら、全要素のメモリコピーが発生し、CPUを無駄に焼き尽くすことになる。
救世主:`System.Collections.Queue`
.NET Frameworkが提供する `Queue` オブジェクトは、内部的に環状バッファ(Circular Buffer)を持っており、要素の追加(Enqueue)と取り出し(Dequeue)が O(1) の定数時間で完了する。
これをVBScriptから `CreateObject(“System.Collections.Queue”)` でインスタンス化することで、レガシーな言語仕様の限界を軽々と突破できるのだ。
—
2. 堅牢なタスク処理パイプラインのアーキテクチャ
今回構築するモジュールは、単にキューを使うだけではない。本番環境(プロダクション)で耐えうるよう、以下の要件を満たしている。
1. トランザクション的安全性: 途中で予期せぬエラー(ファイルのロック、ネットワーク切断など)が発生しても、キューの状態やログが破綻しないこと。
2. デバッグの容易性: 処理の成否、タイムスタンプ、エラー詳細を確実にファイル(または標準出力)に残すこと。
3. メモリリークの防止: 参照の適切な解放。
—
3. 実装コード:プロダクション品質のタスクキュー・エンジン
以下のコードを `TaskPipeline.vbs` として保存してほしい。このまま実務のファイル処理やAPIバッチの骨組みとして流用できる。
‘ ==============================================================================
‘ Script Name: TaskPipeline.vbs
‘ Description: System.Collections.Queue を利用した堅牢な FIFO タスク処理パイプライン
‘ Author: Chief Automation Architect
‘ ==============================================================================
Option Explicit
Main
Sub Main()
Dim fso, logPath
Set fso = CreateObject(“Scripting.FileSystemObject”)
logPath = fso.GetParentFolderName(WScript.ScriptFullName) & “\pipeline_execution.log”
‘ ログファイルの初期化
WriteLog logPath, “=== タスクパイプライン処理開始 ===”
On Error Resume Next
‘ 1. キューオブジェクトの生成 (.NET FrameworkのQueueクラスをバインド)
Dim taskQueue
Set taskQueue = CreateObject(“System.Collections.Queue”)
If Err.Number <> 0 Then
WScript.Echo “致命的エラー: .NET Queue オブジェクトの生成に失敗しました。Error: ” & Err.Description
Exit Sub
End If
‘ 2. モックデータの投入 (Enqueue)
‘ 実際の業務では、ここでDBから未処理レコードを取得したり、フォルダ内のファイルを走査して追加する
Call LoadTasksToQueue(taskQueue)
WriteLog logPath, “キューに格納された総タスク数: ” & taskQueue.Count
‘ 3. パイプライン実行 (Dequeueループ)
Dim currentTask, processedCount, failedCount
processedCount = 0
failedCount = 0
Do While taskQueue.Count > 0
‘ 先頭のタスクを取り出す (FIFO)
currentTask = taskQueue.Dequeue()
‘ 個別タスクの実行とエラーハンドリング
If ExecuteTask(currentTask, logPath) Then
processedCount = processedCount + 1
Else
failedCount = failedCount + 1
End If
Loop
On Error GoTo 0
‘ 4. 終了処理
WriteLog logPath, “=== パイプライン処理終了 ===”
WriteLog logPath, “成功: ” & processedCount & ” 件 / 失敗: ” & failedCount & ” 件”
‘ オブジェクトの明示的な解放
Set taskQueue = Nothing
Set fso = Nothing
WScript.Echo “処理が完了しました。ログを確認してください。” & vbCrLf & logPath
End Sub
‘ ——————————————————————————
‘ タスクをキューにロードする関数(データソース層の模倣)
‘ ——————————————————————————
Sub LoadTasksToQueue(ByRef queue)
Dim i
‘ 例として、処理すべきファイルパスやIDのリストを想定
For i = 1 To 5
queue.Enqueue(“DATA_ID_” & FormatNumber(i, 0, 0, 0, 0) & “.dat”)
Next
‘ 応用: ディレクトリ内のファイルを動的に突っ込む場合もここに実装する
End Sub
‘ ——————————————————————————
‘ 個別タスクの実行ロジック(ビジネスロジック層)
‘ 戻り値: Boolean (True = 成功, False = 失敗)
‘ ——————————————————————————
Function ExecuteTask(ByVal taskData, ByVal logPath)
Dim success
success = True
‘ 業務処理のシミュレーション
‘ ここでファイル操作、DB更新、COMコンポーネントの操作を行う
WriteLog logPath, “処理中 -> タスク: ” & taskData
‘ 例として、特定のデータで意図的にエラーを発生させるテスト(DATA_ID_3)
If taskData = “DATA_ID_3.dat” Then
‘ エラー発生をシミュレート
Err.Raise 9999, “ExecuteTask”, “シミュレートされたファイルアクセス違反”
End If
‘ エラーハンドリングのスコープをこの関数内に限定
If Err.Number <> 0 Then
WriteLog logPath, “[ERROR] タスク失敗 [” & taskData & “] 理由: ” & Err.Description
Err.Clear ‘ エラー状態をクリア
success = False
End If
ExecuteTask = success
End Function
‘ ——————————————————————————
快捷関数: ログ出力 (ファイル連携のベストプラクティス)
‘ ——————————————————————————
Sub WriteLog(ByVal logPath, ByVal message)
Dim fso, stream, timestamp
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ タイムスタンプの付与
timestamp = Now()
‘ ForAppending (8) でファイルを開き、存在しない場合は作成 (True)
‘ ※ファイルロック競合を防ぐため、開きっぱなしにせず都度開閉する設計
On Error Resume Next
Set stream = fso.OpenTextFile(logPath, 8, True)
If Err.Number = 0 Then
stream.WriteLine “[” & timestamp & “] ” & message
stream.Close
End If
Set stream = Nothing
Set fso = Nothing
End Sub
—
4. チーフアーキテクトが教える「現場でハマる罠」と回避策
このアーキテクチャを実務の生産ラインに投入する際、プログラマーが陥りがちな罠と、その回避策を共有しておこう。
トラップ1: ファイル/DB連携における「リソース競合」
複数のタスクが高速にファイルやデータベースにアクセスすると、「Permission Denied(アクセスが拒否されました)」 エラーが発生する。
- 対策: キューの処理ループ内で、データベース接続(ADODB.Connection)やファイルストリームを毎回「開いて閉じる」を繰り返すのは非効率かつ危険だ。DB接続はパイプラインの開始前に1回確立し、ループ内ではトランザクション単位で使い回すか、適切なウェイト(`WScript.Sleep 100` など)を挟んでリソースの解放を待たせる配慮が必要となる。
トラップ2: メモリリークとCOMの参照
VBScriptのガベージコレクションは完璧ではない。特に `System.Collections.Queue` のような外部COMオブジェクトを扱う場合、ループ内で不必要にオブジェクトを生成・破棄すると、メモリリークを引き起こし、長時間のバッチ処理でサーバーが重くなる。
- 対策: オブジェクトのインスタンス化はループの外で行い、ループ内ではデータの出し入れ(Enqueue/Dequeue)のみに専念させること。スクリプトの終端では `Set object = Nothing` を明示的に記述せよ。
トラップ3: キュー自体の永続化(クラッシュ耐性)
メモリ上のキュー(`System.Collections.Queue`)は、スクリプトが予期せぬ電源断や致命的エラーでクラッシュした際、キューに残っていた未処理タスクがすべて消滅するという弱点を持つ。
- 対策: もし「絶対にタスクをロストしてはならない」ミッションクリティカルな要件であれば、キューに入れる前に一度DBやローカルのJSON/CSVファイルにステータス「未処理」として書き込み、処理が完了したら「完了」にアップデートする永続化パターン(Persistent Queue)を併用すべきだ。今回のインメモリキューは、あくまで「高速な揮発性パイプライン」として割り切って使うのが最も美しい。
—
総括
VBScriptであっても、モダンなデータ構造(`System.Collections.Queue`)を適切に選択し、堅牢なエラーハンドリングとログ設計を行えば、立派なエンタープライズ・グレードのタスク処理エンジンへと昇華させることができる。
「古い言語だから仕方ない」と諦める前に、アーキテクチャの力でコードを洗練させろ。君たちの手で、退屈な定型業務を完璧に制圧してくれることを期待している。
