Фоновые задания выполняются в независимых дочерних процессах, которые практически не разделяют состояние с вызывающим; конкретно:
Они не видят ни функций и псевдонимов, определенных в вызывающем сеансе, ни импортированных вручную модулей, ни загруженных вручную сборок .NET.
Они не загружают (исходный код) ваши $PROFILE
файлы, поэтому они не увидят никаких определений оттуда.
В PowerShell версии 6.x и ниже (включая Windows PowerShell) даже текущее расположение (каталог) не было унаследовано от вызывающего (по умолчанию [Environment]::GetFolderPath('MyDocuments')
); это было исправлено в версии 7.0.
Единственный аспект состояния вызывающего сеанса, который они действительно видят, - это копии переменных среды вызывающего процесса.
Чтобы сделать значения переменных из сеанса вызывающего абонента доступными для фонового задания, на них необходимо ссылаться через $using:scope
(см. _ 4_).
- Note that with values other than strings, primitive types (such as numbers), and a handful of other well-known types, this can involve a loss of type fidelity, because the values are marshaled across process boundaries using PowerShell's XML-based serialization and deserialization; this potential loss of type fidelity also affects output from the job - see this answer for background information.
- Использование гораздо более быстрых и менее ресурсоемких заданий thread через _ 5_, позволяет избежать этой проблемы (хотя действуют все остальные ограничения);
Start-ThreadJob
поставляется с PowerShell [Core] 6+ и может быть установлен по запросу в Windows PowerShell (например, Install-Module -Scope CurrentUser ThreadJob
) - см. этот ответ для справочной информации.
Важно: всякий раз, когда вы используете задания для автоматизации, например, в сценарии, вызываемом из планировщика задач Windows или в контексте CI / CD, убедитесь, что вы дождитесь завершения всех заданий перед выходом из сценария (через _ 8_ или _ 9_), потому что сценарий, вызываемый через CLI полностью завершает процесс PowerShell, что уничтожает все незавершенные задания.
Следовательно, если только команда MakeARestCall
:
оказывается файлом сценария (MakeARestCall.ps1
) или исполняемым (MakeARestCall.exe
), расположенным в одном из каталогов, перечисленных в $env:Path
оказывается функцией, определенной в модуле, который загружается автоматически,
ваш $doJob
блок сценария завершится ошибкой при выполнении в процессе задания ', учитывая, что ни функция MakeARestCall
, ни псевдоним не будут определены.
Ваши комментарии предполагают, что MakeARestCall
действительно является функцией, поэтому для того, чтобы ваш код работал, вам придется (пере) определить функцию как часть выполняемого блока скрипта. по работе ($doJob
, в вашем случае):
Следующий упрощенный пример демонстрирует эту технику:
# Sample function that simply echoes its argument.
function MakeARestCall { param($MyParam) "MakeARestCall: $MyParam" }
'foo', 'bar' | ForEach-Object {
# Note: If Start-ThreadJob is available, use it instead of Start-Job,
# for much better performance and resource efficiency.
Start-Job -ArgumentList $_ {
Param([string] $myparam)
# Redefine the function via its definition in the caller's scope.
# $function:MakeARestCall returns MakeARestCall's function body
# which $using: retrieves from the caller's scope, assigning to
# it defines the function in the job's scope.
$function:MakeARestCall = $using:function:MakeARestCall
# Call the recreated MakeARestCall function with the parameter.
MakeARestCall -MyParam $myparam
}
} | Receive-Job -Wait -AutoRemove
Вышеупомянутые выходные данные MakeARestCall: foo
и MakeARestCall: bar
демонстрируют, что (переопределенная) функция MakeARestCall
была успешно вызвана в процессе задания.
Альтернативный подход:
Сделайте MakeARestCall
скрипт (MakeARestCall.ps1
) и на всякий случай вызовите его по его полному пути.
Например, если ваш скрипт находится в той же папке, что и вызывающий скрипт, вызывайте его как
& $using:PSScriptRoot\MakeARestCall.ps1 -MyParam $myParam
Конечно, если вы либо не против дублирования определения функции, либо только нуждаетесь в этом в контексте фоновых заданий, вы можете просто встроить определение функции непосредственно в блок скрипта.
Более простая и быстрая альтернатива PowerShell [Core] 7+, использующая ForEach-Object -Parallel
:
Параметр -Parallel
, представленный в _ 27_ в PowerShell 7 запускает указанный блок сценария в отдельном пространстве выполнения (потоке) для каждого входного объекта конвейера.
По сути, это более простой и удобный способ использования потоковых заданий (Start-ThreadJob
) с такими же преимуществами производительности и использования ресурсов по сравнению с фоновыми заданиями, и с добавленной простотой прямого отчета о выходе потоков.
Однако отсутствие совместного использования состояния, описанное выше в отношении фоновых заданий, также применяется к потоковым заданиям (даже если они выполняются в тот же процесс, они делают это в изолированных пространствах выполнения PowerShell), поэтому здесь тоже функция MakARestCall
должна быть (пере) определена (или встроена) в блок скрипта [1].
# Sample function that simply echoes its argument.
function MakeARestCall { param($MyParam) "MakeARestCall: $MyParam" }
# Get the function definition (body) *as a string*.
# This is necessary, because the ForEach-Object -Parallel explicitly
# disallows referencing *script block* values via $using:
$funcDef = $function:MakeARestCall.ToString()
'foo', 'bar' | ForEach-Object -Parallel {
$function:MakeARestCall = $using:funcDef
MakeARestCall -MyParam $_
}
Синтаксическая ошибка: -Parallel
не является переключателем (параметр типа флага), но принимает блок скрипта для параллельного выполнения в качестве аргумента; другими словами: -Parallel
должен быть помещен непосредственно перед блоком скрипта.
Вышеупомянутое напрямую испускает выходные данные из параллельных потоков по мере их поступления - но обратите внимание, что это означает, что выход не гарантированно поступит в порядке ввода; то есть поток, созданный позже, может ситуативно вернуть свой вывод раньше, чем более ранний поток.
Простой пример:
PS> 3, 1 | ForEach-Object -Parallel { Start-Sleep $_; "$_" }
1 # !! *Second* input's thread produced output *first*.
3
Чтобы отображать выходные данные в порядке ввода, что неизменно требует ожидания завершения всех потоков перед отображением вывода, вы можете добавить -AsJob
переключатель:
- Вместо прямого вывода затем возвращается простой (поточный) объект задания, который возвращает одно задание типа
PSTaskJob
, содержащее несколько дочерних em> jobs, по одному на каждое параллельное пространство выполнения (поток); вы можете управлять им с помощью обычных *-Job
командлетов и получать доступ к отдельным дочерним заданиям через свойство .ChildJobs
.
Дождавшись завершения всего задания, получая его выходные данные через _ 38_ затем показывает их в порядке ввода:
PS> 3, 1 | ForEach-Object -AsJob -Parallel { Start-Sleep $_; "$_" } |
Receive-Job -Wait -AutoRemove
3 # OK, first input's output shown first, due to having waited.
1
[1] В качестве альтернативы, переопределите вашу MakeARestCall
функцию как функцию фильтра (Filter
), которая неявно работает с вводом конвейера через $_
, чтобы вы могли использовать ее определение как ForEach-Object -Parallel
скрипт блокировать как есть:
# Sample *filter* function that echoes the pipeline input it is given.
Filter MakeARestCall { "MakeARestCall: $_" }
# Pass the filter function's definition (which is a script block)
# directly to ForEach-Object -Parallel
'foo', 'bar' | ForEach-Object -Parallel $function:MakeARestCall
person
mklement0
schedule
13.03.2020