Как начать задание функции, которую я только что определил?

Как начать задание функции, которую я только что определил?

function FOO { write-host "HEY" } Start-Job -ScriptBlock { FOO } |
Receive-Job

Receive-Job: The term 'FOO' is not recognized as the name of cmdlet,
function ,script file or operable program.

Что я делаю? Благодарю.

9 ответов

Решение

Как указывает @Shay, FOO должен быть определен для работы. Еще один способ сделать это - использовать -InitializationScript Параметр для подготовки сеанса.

Для вашего примера:

$functions = {
    function FOO { write-host "HEY" }
}

Start-Job -InitializationScript $functions -ScriptBlock {FOO}|
    Wait-Job| Receive-Job

Это может быть полезно, если вы хотите использовать одни и те же функции для разных заданий.

Предложение @Rynant о InitializationScript отлично

Я думал, что назначение (скриптовых) блоков состоит в том, чтобы вы могли передавать их. Поэтому, в зависимости от того, как вы это делаете, я бы сказал:

$FOO = {write-host "HEY"}

Start-Job -ScriptBlock $FOO | wait-job |Receive-Job

Конечно, вы также можете параметризовать блоки скриптов:

$foo = {param($bar) write-host $bar}

Start-Job -ScriptBlock $foo -ArgumentList "HEY" | wait-job | receive-job

Для меня это сработало как:

Start-Job -ScriptBlock ${Function:FOO}

Улучшение ответа @Rynant:

Вы можете определить функцию как обычно в основной части вашего скрипта:

Function FOO 
{ 
  Write-Host "HEY" 
} 

а затем перезапустите это определение в блоке скриптов:

$export_functions = [scriptblock]::Create(@"
  Function Foo { $function:FOO }
"@)

(имеет больше смысла, если у вас есть существенное тело функции), а затем передать их Start-Job как указано выше:

Start-Job -ScriptBlock {FOO} -InitializationScript $export_functions| Wait-Job | Receive-Job

Мне нравится этот способ, так как легче отлаживать задания, выполняя их локально под отладчиком.

Функция должна быть внутри блока скрипта:

Start-Job -ScriptBlock { function FOO { write-host "HEY" } ; FOO } | Wait-Job | Receive-Job

Пока функция, переданная в параметр InitializationScript в Start-Job, невелика, ответ Rynant будет работать, но если функция велика, вы можете столкнуться с приведенной ниже ошибкой.

[localhost] Ошибка при запуске фонового процесса. Сообщается об ошибке: имя файла или расширение слишком длинное"

Лучшей альтернативой является захват определения функции и последующее использование Invoke-Expression в ScriptBlock.

      function Get-Foo {
    param
    (
        [string]$output
    )

    Write-Output $output
}

$getFooFunc = $(Get-Command Get-Foo).Definition

Start-Job -ScriptBlock {
    Invoke-Expression "function Get-Foo {$using:getFooFunc}"
    Get-Foo -output "bar"
}

Get-Job | Receive-Job

PS C:\Users\rohopkin> Get-Job | Receive-Job
bar

В существующих ответах есть хорошая информация, но позвольте мне попытаться систематизировать:

  • Фоновые задания PowerShell [1] выполняются во внепроцессном пространстве выполнения (скрытый дочерний процесс) и поэтому не имеют общего состояния с вызывающим объектом .

  • Поэтому, functionопределения , созданные вызывающим объектом во время сеанса, не видны фоновым заданиям и должны быть воссозданы в контексте задания. [2]

  • Самый простой способ воссоздать определение функции — объединить обозначение переменной пространства имен (например, — см. этот ответ ) с $using:scope, как показано ниже.

    • К сожалению, из-за давней ошибки не работают ссылки в блоках скриптов, переданных в(а такжех) -InitializationScriptпараметр на момент написания этой статьи (Windows PowerShell, PowerShell (Core) 7.3.6) — см. выпуск GitHub № 4530.

Самостоятельный пример:

      function FOO { "HEY" }

Start-Job -ScriptBlock { 

  # Redefine function FOO in the context of this job.
  $function:FOO = "$using:function:FOO" 
  
  # Now FOO can be invoked.
  FOO

} | Receive-Job -Wait -AutoRemoveJob

Вышеуказанная строка выводаHEY, как предполагалось.

Примечание:

  • Присвоение неявно создает функцию (по требованию) и делает присвоенное значение телом функции ; присвоенное значение может быть либо экземпляр или , т.е. текст исходного кода .

  • Ссылки $function:FOOизвлекает тело ранее существовавшей функции в качестве экземпляра. Добавление области видимости () возвращает телоFOOфункция из области вызывающего абонента .

  • Примечание:

    • Из-за$using:, это не экземпляр, а[string]в случае из-за - удивительного - способа, которым[scriptblock]экземпляры десериализуются при межпроцессной сериализации; такое поведение было заявлено как специальное — обсуждение см. в выпуске GitHub № 11698 .

    • Таким образом,"..."вокруг избыточны для , но не для , где сериализация не используется, а воссоздание тела из строки необходимо во избежание повреждения состояния ( подробности см. в выпуске GitHub #16461).

      • Тот факт, что позволяет$using:function:FOOссылки вообще, вероятно, являются недосмотром , учитывая, что они были явно запрещены в блоках сценариев, используемых с (PowerShell v7+) — см. GitHub Issue #12378 .

      • Следовательно, сForEach-Object -Parallelтребуется вспомогательная переменная, которая сначала преобразует тело функции в строку на стороне вызывающей стороны — см. этот ответ .


[1] Этот ответ применим не только к заданиям на основе дочерних процессов, созданным Start-Job, но аналогично обычно предпочтительным заданиям на основе потоков , созданным с помощью Start-ThreadJobи параллелизм на основе потоков, доступный в PowerShell (Core) 7+ с ForEach-Object -Parallel, а также для удаленного взаимодействия PowerShell через Invoke-CommandКороче говоря: это применимо к любому сценарию, в котором PowerShell выполняется вне пространства выполнения (в другом пространстве выполнения).

[2] Альтернативой является предоставление таких определений через файлы сценариев ( *.ps1) или модули , которые задание должно будет иметь точечный источник ( . ) или импортировать.

Комментарий @Ben Power под принятым ответом меня тоже беспокоил, поэтому я погуглил, как получить определения функций, и нашел Get-Command- хотя это получает только тело функции. Но его также можно использовать, если функция поступает откуда-то еще, например, из файла с точкой. Итак, я придумал следующее (придерживайтесь моего соглашения об именах:)), идея состоит в том, чтобы перестроить определения функций, разделенные символами новой строки:

Filter Greeting {param ([string]$Greeting) return $Greeting}
Filter FullName {param ([string]$FirstName, [string]$LastName) return $FirstName + " " + $LastName}
$ScriptText = ""
$ScriptText += "Filter Greeting {" + (Get-Command Greeting).Definition + "}`n"
$ScriptText += "Filter FullName {" + (Get-Command FullName).Definition + "}`n"
$Job = Start-Job `
            -InitializationScript $([ScriptBlock]::Create($ScriptText)) `
            -ScriptBlock {(Greeting -Greeting "Hello") + " " + (FullName -FirstName "PowerShell" -LastName "Programmer")}
$Result = $Job | Wait-Job | Receive-Job
$Result
$Job | Remove-Job

Немного другой взгляд. Функция - это просто блок сценария, назначенный переменной. О, это должна быть работа с потоками. Это не может быть foreach-object -parallel.

$func = { 'hi' } # or
function hi { 'hi' }; $func = $function:hi

start-threadjob { & $using:func } | receive-job -auto -wait

hi
Другие вопросы по тегам