Как передать пользовательскую функцию внутри ForEach-Object -Parallel

Я не могу найти способ передать функцию. Просто переменные.

Есть идеи без помещения функции в цикл ForEach?

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

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

введите описание изображения здесь


person smark91    schedule 17.04.2020    source источник
comment
Либо упакуйте свою функцию в модуль, либо (пере) определите ее внутри блока -Parallel   -  person Mathias R. Jessen    schedule 17.04.2020
comment
В стороне: Write-Host обычно не подходит для используйте, если только намерение не состоит в том, чтобы записать только для отображения, минуя поток вывода успеха и вместе с ним возможность отправлять вывод другим командам, захватывать его в переменной, перенаправлять на файл. Чтобы вывести значение, используйте его отдельно; например, $value вместо Write-Host $value (или используйте Write-Output $value, хотя это редко требуется). См. Также: нижний раздел stackoverflow.com/a/50416448/45375   -  person mklement0    schedule 17.04.2020


Ответы (2)


Решение не такое простое, как можно было бы надеяться:

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 $_
}
  • Этот подход необходим, потому что - помимо текущего местоположения (рабочего каталога) и переменных среды (которые применяются ко всему процессу) - потоки, которые создает ForEach-Object -Parallel, не видят состояние вызывающего, в частности, ни в одном из них. к переменным или функциям (а также не к пользовательским дискам PS и импортированным модулям).

    • Update: js2010's helpful answer shows a more straightforward solution that passes a System.Management.Automation.FunctionInfo, obtained via Get-Command, which can be invoked directly with &. The only caveat is that the original function should be side-effect-free, i.e. should operate solely based on parameter or pipeline inputs, without relying on the caller's state, notably its variables, as that could lead to thread-safety issues. The stringification technique above implicitly prevents any problematic references to the caller's state, because the function body is rebuilt in each thread's context.
  • Начиная с PowerShell 7.1, обсуждается на GitHub для поддержки копирования состояния вызывающего абонента. в потоки по запросу, что сделает доступными функции вызывающего.

Обратите внимание, что обходятся без доп. $funcDef и попытаться переопределить функцию с помощью $function:CustomFunction = $using:function:CustomFunction заманчиво, но $function:CustomFunction - это блок сценария, и использование блоков сценария с указателем области $using: явно запрещено.

$function:CustomFunction - это экземпляр нотации переменных пространства имен, который позволяет вам как получить функцию (ее body как экземпляр [scriptblock]) и установить (определить) его, назначив либо [scriptblock], либо строку, содержащую тело функции.

person mklement0    schedule 17.04.2020
comment
Большое тебе спасибо. Это не самое чистое решение, на которое я надеялся, но оно работает. На стороне производительности каждая итерация в основном создает экземпляр новой функции. Это было похоже на вставку функции внутри foreach, но визуально более чище, не так ли? - person smark91; 17.04.2020
comment
Рад слышать, что это было полезно, @ smark91. Этот метод в первую очередь полезен, если у вас есть уже существующая функция, которую вы хотите использовать в блоке ForEach-Object -Parallel; прямая вставка определения функции, вероятно, быстрее, хотя я не уверен, что это имеет большое значение на практике. - person mklement0; 17.04.2020
comment
Все это отлично подходит для одноразовых приложений, но если у вас есть несколько импортированных модулей, определено больше функций, переменные в воздухе, по сути, целый карточный домик, это слишком много проблем и слишком подвержено ошибкам. Надеемся, что команда PowerShell Core решит сделать копирование пространства выполнения опцией. - person Max Cascone; 21.04.2021

Я только что придумал другой способ, используя get-команду, которая работает с оператором вызова. $ a оказывается объектом FunctionInfo. РЕДАКТИРОВАТЬ: мне сказали, что это не потокобезопасно, но я не понимаю, почему.

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

hi
hi
hi
person js2010    schedule 25.11.2020
comment
Красиво сделано; хотя в принципе могут быть проблемы с потокобезопасностью, все должно быть в порядке, если функция не имеет побочных эффектов, как ваш пример (т.е. пока она работает только с параметрами или конвейерный ввод и не зависит от состояния вызывающего (особенно в отношении его переменных)). - person mklement0; 05.07.2021