質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
PowerShell

Windows PowerShellはコマンドラインインターフェースであり、システム管理を含むWindowsタスク自動化のためのスクリプト言語です。

Q&A

解決済

1回答

4240閲覧

PowerShellで 非同期処理 での ref変数へのデータ追加が無視される事があるのですが 確実にref変数へのデータ追加は出来ないのでしょうか?

kamikazelight

総合スコア305

PowerShell

Windows PowerShellはコマンドラインインターフェースであり、システム管理を含むWindowsタスク自動化のためのスクリプト言語です。

0グッド

0クリップ

投稿2018/11/07 07:11

編集2018/11/07 09:26

前提・実現したいこと

非同期で実行した処理結果を参照渡しの変数に追加

発生している問題

データの追加がされていないことがある上に
エラーも発生しない

問題 確認用 サンプルソースコード

無駄に処理を分けてますが確認のためです...
時間が掛かりすぎる場合は$Aryの行数を減らしてください。

powershell

1<# 2 情報源 3 guitarrapc_tech 様 4 タイトル : PowerShell による同期処理、非同期処理、並列処理 を考えてみる 5 URL : http://tech.guitarrapc.com/entry/2013/10/29/100946 6 7 *** 様 8 タイトル : [properties] Powershellクラスはget setプロパティを実装します 9 URL : https://code-examples.net/ja/q/25e096e 10#> 11using namespace System.Management.Automation.Runspaces; 12using namespace System.Collections; 13using namespace System.Threading; 14 15# クラスを定義します 16class _MultiThread 17{ 18 19 # ノーマル プロパティ 20 [array] $RunspaceCollection 21 22 # 非表示 プロパティ 23 hidden [RunspacePool] $runspacePool 24 25 # 完了 プロパティ 26 hidden [bool]$_AllCompleted = $($this | Add-Member ScriptProperty -Name 'AllCompleted' -Value { 27 $($this.runspaceCollection.RunSpace | sort IsCompleted -Unique).IsCompleted -eq $true 28 }) 29 30 # コンストラクター 31 _MultiThread ($MyHost) 32 { 33 # create Runspace 34 [initialsessionstate] $sessionstate = [initialsessionstate]::CreateDefault() 35 [int] $minPoolSize = [int] $maxPoolSize = 1000 36 $this.runspacePool = [runspacefactory]::CreateRunspacePool($minPoolSize, $maxPoolSize, $sessionstate, $MyHost) # create Runspace Pool 37 $this.runspacePool.ApartmentState = "STA" 38 $this.runspacePool.Open() # open pool 39 } 40 41 # スレッドの追加 42 [void]AddThread([scriptblock]$ScriptBlock) 43 { 44 # Main Invokation 45 [powershell] $powershell = [PowerShell]::Create().AddScript($ScriptBlock) 46 $powershell.RunspacePool = $this.runspacePool 47 $this.RunspaceCollection += New-Object -TypeName PSObject -Property @{ 48 Runspace = $powershell.BeginInvoke(); 49 powershell = $powershell 50 } 51 $powershell.Dispose() 52 } 53 54 # スレッドの追加 55 [void]AddThread([scriptblock]$ScriptBlock,[psobject]$Parameters) 56 { 57 # Main Invokation 58 [powershell] $powershell = [PowerShell]::Create().AddScript($ScriptBlock).AddParameters($Parameters) 59 $powershell.RunspacePool = $this.runspacePool 60 $this.RunspaceCollection += New-Object -TypeName PSObject -Property $( 61 [ordered]@{ 62 Runspace = $powershell.BeginInvoke(); 63 powershell = $powershell 64 } 65 ) 66 } 67 68 69 # 結果の取得 70 [object]GetResult() 71 { 72 # get process result and end powershell session 73 return $(foreach ($runspace in $this.runspaceCollection) 74 { 75 # get reuslt 76 $runspace.powershell.EndInvoke($runspace.Runspace) 77 }) 78 } 79 80 # デストラクタの代わり手動で実行 81 [void]Dispose() 82 { 83 if (!($this.runspacePool.runspacePool.IsDisposed)) 84 { 85 foreach ($Runspace in $this.RunspaceCollection) 86 { 87 $Runspace.powershell.Dispose() 88 } 89 90 # Dispose Runspace 91 $this.runspacePool.Dispose() 92 } 93 } 94} 95 96function _New_MultiThread() 97{ 98[OutputType([_MultiThread])] 99 $Thread = New-Object _MultiThread($host) 100 return $Thread 101} 102 103 104#検索対象データの読み込み 105$_Sarch_List_Read = { 106 107 Param 108 ( 109 # 読み込む二次元配列 110 [Parameter(Mandatory, 111 Position=0)] 112 [ref] 113 $Ary, 114 115 # 読み込み開始位置 116 [Parameter(Mandatory, 117 Position=1)] 118 [int] 119 $Start, 120 121 # 読み込み終了位置 122 [Parameter(Mandatory, 123 Position=2)] 124 [int] 125 $End, 126 127 # 取得データの格納変数 128 [Parameter(Mandatory, 129 Position=3)] 130 [ref] 131 $CSV 132 ) 133 for ($i = $Start; $i -le $End; $i++) 134 { 135 $CSV_Data = New-Object psobject | select -Property No,Category,Type,PutName,ListPrice,Purchase,PrimeCost,SellingPrice,Maker,DisplayOrderNo,EquipmentName,CharacterSymbol 136 $CSV_Data.No = $Ary.Value[$i,1] 137 $CSV_Data.Category = $Ary.Value[$i,2] 138 $CSV_Data.Type = $Ary.Value[$i,3] 139 $CSV_Data.PutName = $Ary.Value[$i,4] 140 $CSV_Data.ListPrice = $Ary.Value[$i,5] 141 $CSV_Data.Purchase = $Ary.Value[$i,6] 142 $CSV_Data.PrimeCost = $Ary.Value[$i,7] 143 $CSV_Data.SellingPrice = $Ary.Value[$i,8] 144 $CSV_Data.Maker = $Ary.Value[$i,9] 145 $CSV_Data.DisplayOrderNo = $Ary.Value[$i,10] 146 $CSV_Data.EquipmentName = $Ary.Value[$i,11] 147 $CSV_Data.CharacterSymbol = $Ary.Value[$i,12] 148 149 $CSV.Value += $CSV_Data 150 151 } 152} 153 154# 20000 x 12 の配列を作成 元がExcelから持ってきてるので 添え字は1から始まりと仮定する 155$Ary = New-Object -TypeName 'System.Object[,]' -ArgumentList 20001,13 156$i = 0 157 158# 適当にデータ代入 159foreach ($item in $Ary) 160{ 161 $_ = $i 162 $i++ 163} 164 165 166 $MultiThread = _New_MultiThread 167 168Write-Host "検索テーブルを読み込んでいます..." 169$Search = @() 170$i = 0 1711..$Ary.GetUpperBound(0) | foreach { 172 $i++ 173 $MultiThread.AddThread($_Sarch_List_Read,[ordered]@{ 174 Ary = [ref]$Ary 175 Start = $_ 176 End = $_ 177 CSV = [ref]$Search 178 }) 179} 180Write-host "処理を実行した回数 = $i" 181 182while (!($MultiThread.AllCompleted)){} 183$MultiThread.Dispose() 184Write-host "登録されたデータ数 = $($Search.Count)" 185

試したこと

上記 サンプルコードを作成、および実行して データの内容は関係ないことの確認
実行するたびに登録されたデータ数が変わる...

ひとまず refつかっての受け取りは諦めて
処理完了後にシリアル処理で戻り値を順に変数に格納~~しようと思っています。~~しました。

powershell

1 2 3function _New_MultiThread() 4{ 5[OutputType([_MultiThread])] 6 $Thread = New-Object _MultiThread($host) 7 return $Thread 8} 9 10 11#検索対象データの読み込み 12$_Sarch_List_Read = { 13 14 Param 15 ( 16 # 読み込む二次元配列 17 [Parameter(Mandatory, 18 Position=0)] 19 $Ary, 20 21 # 読み込み開始位置 22 [Parameter(Mandatory, 23 Position=1)] 24 [int] 25 $Start, 26 27 # 読み込み終了位置 28 [Parameter(Mandatory, 29 Position=2)] 30 [int] 31 $End 32 ) 33 for ($i = $Start; $i -le $End; $i++) 34 { 35 $CSV_Data = New-Object psobject | select -Property No,Category,Type,PutName,ListPrice,Purchase,PrimeCost,SellingPrice,Maker,DisplayOrderNo,EquipmentName,CharacterSymbol 36 $CSV_Data.No = $Ary[$i,1] 37 $CSV_Data.Category = $Ary[$i,2] 38 $CSV_Data.Type = $Ary[$i,3] 39 $CSV_Data.PutName = $Ary[$i,4] 40 $CSV_Data.ListPrice = $Ary[$i,5] 41 $CSV_Data.Purchase = $Ary[$i,6] 42 $CSV_Data.PrimeCost = $Ary[$i,7] 43 $CSV_Data.SellingPrice = $Ary[$i,8] 44 $CSV_Data.Maker = $Ary[$i,9] 45 $CSV_Data.DisplayOrderNo = $Ary[$i,10] 46 $CSV_Data.EquipmentName = $Ary[$i,11] 47 $CSV_Data.CharacterSymbol = $Ary[$i,12] 48 49 $CSV_Data 50 51 } 52} 53 54# 20000 x 12 の配列を作成 元がExcelから持ってきてるので 添え字は1から始まりと仮定する 55$Ary = New-Object -TypeName 'System.Object[,]' -ArgumentList 20001,13 56$i = 0 57 58# 適当にデータ代入 59foreach ($item in $Ary) 60{ 61 $_ = $i 62 $i++ 63} 64 65 66 $MultiThread = _New_MultiThread 67 68Write-Host "検索テーブルを読み込んでいます..." 69$Max = $Ary.GetUpperBound(0) 70Write-host "対象データ行数 = $($Max)" 71$Step = 2000 72for ($i = 1; $i -le $Max; $i+=$Step) 73{ 74 if ($($i + $Step - 1) -le $Max) 75 { 76 $End = $($i + $Step - 1) 77 } 78 else 79 { 80 $End = $Max 81 } 82 $MultiThread.AddThread($_Sarch_List_Read,[ordered]@{ 83 Ary = $Ary 84 Start = $i 85 End = $End 86 }) 87} 88 89while (!($MultiThread.AllCompleted)){} 90$Search = @($MultiThread.GetResult() | foreach {$_}) 91$MultiThread.Dispose() 92Write-host "登録されたデータ数 = $($Search.Count)" 93

補足情報(FW/ツールのバージョンなど)

Win10

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

PowerShellの配列に対する+=演算子は、新しく配列を作り直す処理のはずなので、作り直す間に他の処理が入ると追加に失敗する、と予想しました。

非同期処理なら…ということで
$Search = @()
$Search = New-Object -TypeName Concurrent.ConcurrentQueue[psobject]
へ変更し、
$_Sarch_List_Read内の$CSV.Value += $CSV_Data
while( -not $CSV.Value.TryAdd($CSV_Data) ) { }
へ変更したところ、数が一致するようになりました。
非同期処理は詳しくないので、もっと良い方法があるかもしれませんが。

また、この場合[ref]にせず直接キューの参照を渡しても、「登録されたデータ数」の値は「処理を実行した回数」と一致しました。

投稿2018/11/07 12:59

imihito

総合スコア2166

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kamikazelight

2018/11/07 23:04 編集

ありがとうございます。 ちゃんとマルチスレッド処理等用のオブジェクトがあったのですね。 全く知りませんでした。 今の作業がひと段落してから試してみたいと思います。 分からない事があるのですが「直接キューの参照を渡す」には どうしたらいいのでしょうか? そもそも「キュー」自体 どっかの書籍でデータを入れた順番で取り出せる変数? 的な内容が書いてあったのを見たことがあるだけで 使ったことがあるのかさえ分かりません。 PowerShellで使えるのでしょうか? 是非 教えて頂きたいです。 ...別の質問で再度投稿したほうがいいでしょうか?
imihito

2018/11/08 11:38

「キュー」に関しては私の表記ぶれです、すみません。 ConcurrentQueue→スレッドセーフなキュー 回答中の「ConcurrentQueue」と「キュー」は同じものを示しています。 また、スレッドセーフなコレクションであればなんでも良かったので、その中から「ConcurrentQueue」を選択した理由は特にありません。 https://docs.microsoft.com/ja-jp/dotnet/standard/collections/thread-safe/ 「ConcurrentQueue」などはクラス=参照型のため、[ref]を付けずに関数に渡しても、オブジェクトのコピーではなく、そのオブジェクトへの参照(データ本体ではなくデータへのリンクのイメージ)が渡されます。 他の関数に渡しても、渡しているのはリンクで元のオブジェクトは一つのため、`TryAdd`などのメソッドでオブジェクトの中身を変更できるということになります。 # 配列で[ref]が必要な理由 VBAと異なり、配列も参照型(正確にはVBAの挙動が特殊例)ですが、配列に対して+=演算子を使うと新しく配列を作り直します。 元の配列とは別のものになるため、引数としてもらったリンクそのものを書き換える必要があるため[ref]にしないといけません。
kamikazelight

2018/11/09 02:42

おかげさまでイメージが湧きました。 分かりやすい回答ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問