【问题标题】:How to pass a custom function inside a ForEach-Object -Parallel如何在 ForEach-Object -Parallel 中传递自定义函数
【发布时间】:2020-04-17 13:46:47
【问题描述】:

我找不到传递函数的方法。只是变量。

没有将函数放在 ForEach 循环中的任何想法?

function CustomFunction {
    Param (
        $A
    )
    Write-Host $A
}

$List = "Apple", "Banana", "Grape" 
$List | ForEach-Object -Parallel {
    Write-Host $using:CustomFunction $_
}

【问题讨论】:

  • 要么将你的函数打包到一个模块中,要么(重新)定义它-Parallel
  • 顺便说一句:Write-Host is typically the wrong tool to use,除非意图是仅写入显示器,绕过成功输出流,并能够将输出发送到其他命令,将其捕获在变量中,将其重定向到文件。要输出一个值,请单独使用它;例如,$value 而不是 Write-Host $value(或使用 Write-Output $value,尽管这很少需要)。另见:stackoverflow.com/a/50416448/45375的底部

标签: powershell powershell-core


【解决方案1】:

解决方案并不像人们希望的那么简单:

function CustomFunction {
    Param ($A)
    "[$A]"
}

# Get the function's definition *as a string*
$funcDef = $function:CustomFunction.ToString()

"Apple", "Banana", "Grape"  | ForEach-Object -Parallel {
    # Define the function inside this thread...
    $function:CustomFunction = $using:funcDef
    # ... and call it.
    CustomFunction $_
}

注意:This answer 包含一个类似的解决方案,用于在 ForEach-Object -Parallel 脚本块中使用调用者范围内的 脚本块

  • 注意:如果您的函数是在 module 中定义的,该模块位于module-autoloading 功能已知的位置之一,则您的函数调用将作为-与ForEach-Object -Parallel 一起使用,无需额外的努力 - 但每个线程都会产生(隐式)导入模块的成本。

  • 上述方法是必要的,因为除了当前位置(工作目录)和环境变量(适用于进程范围)之外,ForEach-Object -Parallel 创建的线程看到调用者的状态,特别是既不涉及变量也不涉及函数(也不是自定义 PS 驱动器和导入模块)。

    • 更新:js2010's helpful answer 显示了一个更直接的解决方案,它传递了一个通过Get-Command 获得的System.Management.Automation.FunctionInfo,可以直接用& 调用。唯一需要注意的是,原始函数应该没有副作用,即应该完全基于参数或管道输入进行操作,而不依赖于调用者的状态,尤其是它的变量,因为这可能会导致线程安全问题。上面的字符串化技术隐式地防止了对调用者状态的任何有问题的引用,因为函数体是在每个线程的上下文中重建的。
  • 从 PowerShell 7.1 开始,an enhancement is being discussed on GitHub 支持将调用者的状态复制到线程按需,这将使调用者的函数可用。

请注意,没有 aux. $funcDef 变量并尝试使用$function:CustomFunction = $using:function:CustomFunction 重新定义函数很诱人,但$function:CustomFunction 是一个脚本块,并且明确禁止使用带有$using: 范围说明符的脚本块。

  • 但是,$function:CustomFunction = $using:function:CustomFunction 可以Start-Job 一起使用;示例见this answer

  • 它也适用于Start-ThreadJob,您甚至可以在其中使用& $using:function:CustomFunction $_,因为$using:function:CustomFunction 被保留为脚本块(与Start-Job 不同,它被反序列化为字符串,这本身就是令人惊讶的行为 - 请参阅GitHub issue #11698)。但是,不清楚设计是否支持此行为,因为它会受到上述相同的潜在跨线程问题的影响。

$function:CustomFunctionnamespace variable notation 的一个实例,它允许您get 一个函数(它的body 作为[scriptblock] 实例)和设置(定义)它,通过分配[scriptblock] 或包含函数体的字符串。

【讨论】:

  • 非常感谢。这不是我希望的更清洁的解决方案,但它有效。性能方面,每次迭代基本上都是在实例化一个新函数。这就像在 foreach 中插入函数,但视觉上更干净,对吧?
  • 很高兴听到它有帮助,@smark91。如果您想在ForEach-Object -Parallel 块中使用预先存在的函数,则该技术主要有用;直接插入函数定义可能会更快,尽管我不确定它在实践中是否有很大的不同。
  • 这一切都非常适合一次性使用,但是如果您导入了多个模块,定义了更多功能,变量悬而未决,基本上是一整套纸牌,那就太麻烦了,太容易发生错误。希望 PowerShell Core 团队决定让运行空间复制成为一种选择。
【解决方案2】:

我刚刚想出了另一种使用 get-command 的方法,它适用于呼叫操作员。 $a 最终成为 FunctionInfo 对象。编辑:有人告诉我这不是线程安全的,但我不明白为什么。

function hi { 'hi' }
$a = get-command hi
1..3 | foreach -parallel { & $using:a }

hi
hi
hi

【讨论】:

  • 干得好;虽然原则上可能存在线程安全问题,但只要该函数没有副作用就应该没问题,就像您的示例一样(即,只要它仅在 parameters 上运行或管道输入,并且不依赖于调用者的状态(尤其是关于其变量)。
【解决方案3】:

所以我想出了另一个可能对尝试动态添加函数的人有用的小技巧,特别是如果您事先不知道它的名称,例如当函数在数组中时。

# Store the current function list in a variable
$initialFunctions=Get-ChildItem Function:

# Source all .ps1 files in the current folder and all subfolders
Get-ChildItem . -Recurse | Where-Object { $_.Name -like '*.ps1' } |
     ForEach-Object { . "$($_.FullName)" }

# Get only the functions that were added above, and store them in an array
$functions = @()
Compare-Object $initialFunctions (Get-ChildItem Function:) -PassThru |
    ForEach-Object { $functions = @($functions) + @($_) }

1..3 | ForEach-Object -Parallel {
    # Pull the $functions array from the outer scope and set each function
    # to its definition
    $using:functions | ForEach-Object {
        Set-Content "Function:$($_.Name)" -Value $_.Definition
    }
    # Call one of the functions in the sourced .ps1 files by name
    SourcedFunction $_
}

主要的“技巧”是使用Set-ContentFunction: 加上函数名,因为PowerShell 本质上将Function: 的每个条目视为路径。

当您考虑Get-PSDrive 的输出时,这是有道理的。因为这些条目中的每一个都可以以相同的方式用作“驱动器”(即,使用冒号)。

【讨论】:

    猜你喜欢
    • 2022-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多