我在更長的腳本中遇到了這個問題,并在此處進行了簡化以顯示重現它所需的最少代碼(我認為)。它輸出數字后跟字母: 1 a 1 b 1 c... 2 a 2 b 2 c... 一直到“500 z”
Function Write-HelloWorld
{
Param($number)
write-host -Object $number
}
$numbers = 1..500
$letters = "a".."z"
$Function = get-command Write-HelloWorld
$numbers | ForEach-Object -Parallel {
${function:Write-HelloWorld} = $using:Function
foreach($letter in $using:letters) {
Write-HelloWorld -number "$_ $letter"
}
}
我偶爾會看到 2 種型別(不是每次運行它時):
- “‘write-host’一詞未被識別為 cmdlet、函式、腳本檔案或可執行程式的名稱。” 據了解,write-host 應該始終可用。在呼叫 write-host 之前添加“Import-Module Microsoft.PowerShell.Utility”行沒有幫助
- 如下所示的奇怪輸出,特別是所有“write-host :”行。
uj5u.com熱心網友回復:
Santiago Squarzon 的有用答案很好地 展示了您的方法存在的問題,并鏈接到解釋潛在問題??(運行空間親和力)的 GitHub 問題;但是,該演示不是正確的解決方案(它不是故意的),因為它使用顯式同步來一次只允許一個執行緒呼叫該函式,這否定了并行性的好處。
至于解決方案:
您必須將您的函式體的字串表示形式傳遞給呼叫Write-HelloWorld
ForEach-Object
-Parallel
:
Function Write-HelloWorld
{
Param($number)
write-host -Object $number
}
$numbers = 1..500
$letters = "a".."z"
# Get the body of the Write-HelloWorld function *as a string*
# Alternative, as suggested by @Santiago:
# $funcDefString = (Get-Command -Type Function Write-HelloWorld).Definition
$funcDefString = ${function:Write-HelloWorld}.ToString()
$numbers | ForEach-Object -Parallel {
# Redefine the Write-HelloWorld function in this thread,
# using the *string* representation of its body.
${function:Write-HelloWorld} = $using:funcDefString
foreach($letter in $using:letters) {
Write-HelloWorld -number "$_ $letter"
}
}
${function:Write-HelloWorld}
是命名空間變數表示法的一個實體,它允許您通過分配包含函式體的 a 或字串來獲取函式(它的主體作為[scriptblock]
實體)和設定(定義)它。[scriptblock]
通過傳遞一個字串,該函式在每個執行緒的背景關系中重新創建[System.Management.Automation.FunctionInfo]
,這避免了當您傳遞一個實體時可能出現的跨執行緒問題Get-Command
,作為輸出,其中包含一個[scriptblock]
系結到定義它的運行空間(即,呼叫者的;據說該腳本塊與呼叫者的運行空間具有親和力),并且從其他執行緒(運行空間)呼叫此系結[scriptblock]
實體是不安全的。
相比之下,通過在每個執行緒中重新定義函式,通過字串,創建系結到該執行緒的特定于執行緒的[scriptblock]
實體,可以安全地呼叫該實體。
事實上,您似乎發現了一個漏洞,因為當您嘗試直接[scriptblock]
將實體與范圍一起使用時,設計命令會中斷并顯示一條明確的錯誤訊息:$using:
A ForEach-Object -Parallel using variable cannot be a script block.
Passed-in script block variables are not supported with ForEach-Object -Parallel,
and can result in undefined behavior
換句話說:PowerShell 甚至不應該讓你做你試圖做的事情,但不幸的是,從 PowerShell Core 7.2.7 開始,這會導致你看到的模糊失敗。
潛在的未來改進:
- GitHub 問題 #12240中正在討論一項增強功能,以支持按需將呼叫者的狀態復制到并行執行緒,這將自動使呼叫者的函式可用,而無需手動重新定義。
uj5u.com熱心網友回復:
請注意,此答案旨在證明一個觀點,但并未提供問題的正確解決方案。
有關解決此問題的正確方法,請參閱mklement0 的有用答案,只需將函式的定義作為字串傳遞給運行空間。有關更多詳細資訊,另請參閱GitHub 問題 #4003。
傳入一個參考物件并在沒有執行緒安全的情況下使用它是一個非常糟糕的主意,這里證明只需在代碼中添加執行緒安全即可解決問題:
function Write-HelloWorld {
param($number)
Write-Host -Object $number
}
$numbers = 1..500
$letters = "a".."z"
$Function = Get-Command Write-HelloWorld
$numbers | ForEach-Object -Parallel {
$refObj = $using:Function
[System.Threading.Monitor]::Enter($refObj)
${function:Write-HelloWorld} = $using:Function
foreach($letter in $using:letters) {
Write-HelloWorld -number "$_ $letter"
}
[System.Threading.Monitor]::Exit($refObj)
}
準確地說,這個問題與Runspace Affinity有關,所有Runspace都試圖將呼叫發送回原始 Runspace 執行緒,因此糟糕的 PowerShell 崩潰了。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/524891.html
標籤:电源外壳