はじめに
PowerShellで、名前付きパイプとRunspaceを使用してプロセス間通信(IPC: Inter-Process Communication)を使って非同期処理を実現する方法について解説する。
スクリプトの概要
このスクリプトは、名前付きパイプを使用してメインプロセスと子プロセス間で通信を行うものである。メインプロセスはユーザーからのコマンドを受け取り、子プロセスに送信する。子プロセスはコマンドに応じた処理を行い、その結果をメインプロセスに返す。
必要なクラスのインポート
まず、並行処理のためのクラスを使用するために、System.Coreアセンブリをインポートする。
1 |
Add-Type -AssemblyName System.Core |
同期ディクショナリの作成
次に、スレッド間で安全に共有できる同期ディクショナリを作成する。ConcurrentDictionaryは複数のスレッドから同時にアクセスしても安全である。
1 2 3 |
$syncHash = [System.Collections.Concurrent.ConcurrentDictionary[string,object]]::new() $syncHash["Exit"] = $false $syncHash["PipeStatus"] = "Initializing" |
RunspacePoolの作成
RunspacePoolを使用することで、PowerShellコマンドを非同期で実行できる。ここでは、1つのスレッドのみを使用する設定にしている。
1 2 |
$runspacePool = [runspacefactory]::CreateRunspacePool(1, 1) $runspacePool.Open() |
子プロセス用のスクリプトブロック
このスクリプトブロックは別のRunspaceで実行され、名前付きパイプサーバーを作成してクライアントからの接続を待つ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
$childScriptBlock = { param($syncHash) $pipeName = "ControlPipe" try { $pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName, [System.IO.Pipes.PipeDirection]::InOut) $syncHash["PipeStatus"] = "Waiting for connection" $pipeServer.WaitForConnection() $syncHash["PipeStatus"] = "Connected" $reader = New-Object System.IO.StreamReader($pipeServer) $writer = New-Object System.IO.StreamWriter($pipeServer) $writer.AutoFlush = $true while (-not $syncHash["Exit"]) { if ($pipeServer.IsConnected -and $pipeServer.CanRead) { $command = $reader.ReadLine() if ($command -ne $null) { switch ($command) { "status" { $writer.WriteLine("Child process is running") } "date" { $writer.WriteLine((Get-Date).ToString("yyyy-MM-dd")) } "help" { $helpMessage = "利用可能なコマンド:status - 子プロセスの状態を確認 date - 現在の日付を表示exit - プログラムを終了 help - このヘルプメッセージを表示" $writer.WriteLine($helpMessage) } "exit" { $syncHash["Exit"] = $true $writer.WriteLine("Exiting child process") } default { $writer.WriteLine("Unknown command: $command") } } } } Start-Sleep -Milliseconds 100 } } finally { if ($pipeServer -ne $null) { $pipeServer.Dispose() } $syncHash["PipeStatus"] = "Closed" } } |
子プロセスの開始
PowerShellオブジェクトを作成し、RunspacePoolを使用して非同期で実行する。
1 2 3 |
$childJob = [powershell]::Create().AddScript($childScriptBlock).AddArgument($syncHash) $childJob.RunspacePool = $runspacePool $childHandle = $childJob.BeginInvoke() |
メインプロセスの処理
メインプロセスは名前付きパイプクライアントを作成して子プロセスに接続し、ユーザーからのコマンドを子プロセスに送信する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
try { Write-Host "子プロセスを起動しました。接続を待っています..." $pipeClient = New-Object System.IO.Pipes.NamedPipeClientStream(".", "ControlPipe", [System.IO.Pipes.PipeDirection]::InOut) $pipeClient.Connect(5000) Write-Host "子プロセスに接続しました" $reader = New-Object System.IO.StreamReader($pipeClient) $writer = New-Object System.IO.StreamWriter($pipeClient) $writer.AutoFlush = $true while (-not $syncHash["Exit"]) { $command = Read-Host "コマンドを入力してください (status, date, help, exit)" if ($command -ne "") { $writer.WriteLine($command) Write-Host "送信: $command" $response = $reader.ReadLine() Write-Host "応答: $response" if ($command -eq "exit") { break } } } } catch { Write-Host "エラーが発生しました: $_" } finally { if ($pipeClient -ne $null) { $pipeClient.Close() $pipeClient.Dispose() } if ($childJob -ne $null) { if (-not $syncHash["Exit"]) { Write-Host "子プロセスがまだ実行中です。終了待ち..." $syncHash["Exit"] = $true Start-Sleep -Seconds 1 } try { Write-Host "子プロセスを終了します..." $childJob.EndInvoke($childHandle) Write-Host "子プロセスが正常に終了しました。" } catch { Write-Host "子プロセスの終了中にエラーが発生しました: $_" } $childJob.Dispose() } if ($runspacePool -ne $null) { $runspacePool.Close() $runspacePool.Dispose() } } Write-Host "メインプロセスが終了しました。" |
まとめ
このスクリプトは、PowerShellでプロセス間通信を実現するための基本的な方法を示している。名前付きパイプとRunspaceを活用することで、非同期処理+プロセス間で通信を行うことができる。
コメント