Фоновые задания выполняются в независимых дочерних процессах, которые практически не имеют общего состояния с вызывающей стороной ; в частности:
Они не видят ни функций, ни псевдонимов, определенных в вызывающем сеансе, ни импортированных вручную модулей, ни загруженных вручную. NET сборок.
Они не загружают (точка-источник) ваши файлы $PROFILE
, поэтому они не увидят никаких определений оттуда.
В версиях PowerShell 6. x и ниже (включая Windows PowerShell), даже текущее местоположение (каталог) не было унаследовано от вызывающей стороны (по умолчанию [Environment]::GetFolderPath('MyDocuments')
); это было исправлено в v7.0.
Аспект only состояния вызывающего сеанса, который они видят, являются копиями вызывающего процесса ' переменных среды .
Чтобы сделать значения переменных из сеанса вызывающего абонента доступными для фонового задания, на них необходимо ссылаться через $using:scope
(см. about_Remote_Variables
) .
- Обратите внимание, что при значениях, отличных от строк, примитивных типов (таких как числа) и нескольких других известных типов, может привести к потере точности типа потому что значения маршалируются через границы процессов с помощью сериализации и десериализации на основе PowerShell XML; эта потенциальная потеря точности типа также влияет на вывод задания - см. этот ответ для справочной информации.
- Использование намного быстрее и менее ресурсоемких потоков заданий через
Start-ThreadJob
позволяет избежать этой проблемы (хотя все другие ограничения применять); Start-ThreadJob
поставляется с PowerShell [Core] 6+ и может быть установлен по требованию в Windows PowerShell (например, Install-Module -Scope CurrentUser ThreadJob
) - см. этот ответ для справочной информации.
Важно : Всякий раз, когда вы используете задания для автоматизации , например, в сценарии, вызываемом из планировщика задач Windows или в в контексте CI / CD убедитесь, что вы ждете завершения всех заданий sh, прежде чем выходить из сценария (через Receive-Job -Wait
или Wait-Job
), поскольку сценарий, вызываемый через PowerShell CLI , выходит из процесса PowerShell в целом, что убивает всех незавершенных заданий.
Следовательно, если команда MakeARestCall
:
окажется файлом сценария (MakeARestCall.ps1
) или исполняемым файлом (MakeARestCall.exe
), расположенным в одна из директорий, перечисленных в $env:Path
, является функцией, определенной в модуле , который автоматически загружается ,
* 11 07 * ваш
$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
функция была успешно вызвана в процессе задания.
альтернативный подход :
Make MakeARestCall
script (MakeARestCall.ps1
) и вызовите его по полному пути , чтобы быть в безопасности.
Например, если ваш скрипт находится в той же папке, что и , вызывая скрипт, вызовите его как
& $using:PSScriptRoot\MakeARestCall.ps1 -MyParam $myParam
Конечно, если вы не против скопировать определение функции или только * 1 151 * это необходимо в контексте фоновых заданий, вы можете просто встроить определение функции непосредственно в блок скрипта.
Более простая и быстрая альтернатива PowerShell [Core] 7+, используя ForEach-Object -Parallel
:
Параметр -Parallel
, введенный в ForEach-Object
в PowerShell 7 , запускает указанный блок сценария в отдельном пространстве выполнения (нить) для каждого входного объекта конвейера.
По сути, это более простой, дружественный к конвейеру способ использования потоковых заданий (Start-ThreadJob
) с тем же преимущества в производительности и использовании ресурсов по сравнению с фоновыми заданиями , а также с добавленной простотой прямого отчета о результатах потоков .
Однако отсутствие совместного использования состояний обсуждалось с в отношении фоновых заданий выше также применимы к потоковым заданиям (даже если они выполняются в том же процессе, они делают это в изолированном PowerShell runspaces ), поэтому и здесь функция 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
не является switch (параметр типа флага), но принимает блок скрипта работать параллельно в качестве аргумента; другими словами: -Parallel
должен быть помещен непосредственно перед блоком сценария.
Вышеизложенное напрямую генерирует выходные данные из параллельных потоков по мере их поступления - но учтите, что это означает, что выходные данные равны не гарантированно поступит в порядке ввода; то есть созданный позже поток может ситуативно вернуть свои выходные данные перед более ранним потоком.
Простой пример:
PS> 3, 1 | ForEach-Object -Parallel { Start-Sleep $_; "$_" }
1 # !! *Second* input's thread produced output *first*.
3
Чтобы показать выходные данные в порядке ввода - который неизменно требует ожидания завершения всех потоков sh перед отображением вывода, вы можете добавить -AsJob
switch :
- Вместо прямого вывода, * 1220 Затем возвращается одиночный, легкий (основанный на потоках) объект задания, который возвращает одиночное задание типа
PSTaskJob
, содержащее несколько дочерних заданий, по одному для каждого параллельного пространства выполнения. (нить); вы можете управлять им с помощью обычных командлетов *-Job
и получать доступ к отдельным дочерним заданиям через свойство .ChildJobs
.
Дождавшись завершения всего задания до завершения , получив его выводится через Receive-Job
, затем показывает их в порядке ввода :
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