Вы действительно ищете Powershell фоновые задания , как советует Lee Daily .
Однако задания являются сложными, потому что каждое задание выполняется в своем собственном процессе , что приводит к значительным накладным расходам.
Модуль ThreadJob
предлагает легкую альтернативу на основе потоков .Он поставляется с PowerShell Core и в Windows PowerShell может быть установлен по требованию, например,
Install-Module ThreadJob -Scope CurrentUser
.
Вы просто звоните Start-ThreadJob
вместо Start-Job
, ииспользуйте стандартные *-Job
командлеты, чтобы управлять такими заданиями потока - так же, как вы управляете обычным фоновым заданием.
Вот пример:
$startedAt = [datetime]::UtcNow
# Define the commands to run as [thread] jobs.
$commands = { $n = 2; Start-Sleep $n; "I ran for $n secs." },
{ $n = 3; Start-Sleep $n; "I ran for $n secs." },
{ $n = 10; Start-Sleep $n; "I ran for $n secs." }
# Start the (thread) jobs.
# You could use `Start-Job` here, but that would be more resource-intensive
# and make the script run considerably longer.
$jobs = $commands | Foreach-Object { Start-ThreadJob $_ }
# Wait until all jobs have completed, passing their output through as it
# is received, and automatically clean up afterwards.
$jobs | Receive-Job -Wait -AutoRemoveJob
"All jobs completed. Total runtime in secs.: $(([datetime]::UtcNow - $startedAt).TotalSeconds)"
Выше приведено что-то вроде следующего;обратите внимание, что вывод отдельных команд сообщается , когда он становится доступным , но выполнение вызывающего скрипта не продолжается до тех пор, пока не будут выполнены все команды:
I ran for 2 secs.
I ran for 3 secs.
I ran for 10 secs.
All jobs completed. Total runtime in secs.: 10.2504931
Примечание : в этом простом случае очевидно, какие выходные данные были получены от какой команды, но, как правило, выходные данные из различных заданий будут запускать непредсказуемо с чередованием , что затрудняет интерпретацию выходных данных - см. Следующий разделдля решения.
Как видите, накладные расходы, вводимые для параллельного выполнения в фоновом режиме, минимальны - общее выполнение заняло всего чуть более 10 секунд, время выполнения самого длительного выполнения3 команды.
Если вместо этого использовать Start-Job
на основе процесса, общее время выполнения может выглядеть примерно так, показывая значительные накладные расходы, особенно при первом запуске фонового задания всеанс:
All jobs completed. Total runtime in secs.: 18.7502717
то есть хотя бы на первом вВо время сеанса преимущества параллельного выполнения в фоновом режиме были сведены на нет - выполнение заняло бы больше времени, чем последовательное выполнение потребовалось бы в этом случае.
В то время как последующие фоновые задания на основе процессов втот же сеанс выполняется быстрее, издержки по-прежнему значительно выше, чем для заданий на основе потоков.
Синхронизация потоков вывода заданий
Если вы хотите показать выходные данные из фоновых команд на команду , вам нужно собирать выходные данные отдельно.
Примечание. В окне консоли (терминале) требуется дождаться завершения всех команд, прежде чем вы сможете отобразить вывод (посколькуневозможно отобразить несколько выходных потоков одновременно с помощью обновления на месте, по крайней мере, с помощью обычных команд вывода).
$startedAt = [datetime]::UtcNow
$commands = { $n = 1; Start-Sleep $n; "I ran for $n secs." },
{ $n = 2; Start-Sleep $n; "I ran for $n secs." },
{ $n = 3; Start-Sleep $n; "I ran for $n secs." }
$jobs = $commands | Foreach-Object { Start-ThreadJob $_ }
# Wait until all jobs have completed.
$null = Wait-Job $jobs
# Collect the output individually for each job and print it.
foreach ($job in $jobs) {
"`n--- Output from {$($job.Command)}:"
Receive-Job $job
}
"`nAll jobs completed. Total runtime in secs.: $('{0:N2}' -f ([datetime]::UtcNow - $startedAt).TotalSeconds)"
Выше будет напечатано что-то вроде этого:
--- Output from { $n = 1; Start-Sleep $n; "I ran for $n secs." }:
I ran for 1 secs.
--- Output from { $n = 2; Start-Sleep $n; "I ran for $n secs." }:
I ran for 2 secs.
--- Output from { $n = 3; Start-Sleep $n; "I ran for $n secs." }:
I ran for 3 secs.
All jobs completed. Total runtime in secs.: 3.09
Использование Start-Process
для запуска команд в отдельных окнах
В Windows вы можете использовать Start-Process
(wпсевдоним шланга start
) для запуска команд в новом окне , которое по умолчанию также асинхронно , т. е. последовательно запущенные команды действительно запускаются параллельно .
В ограниченном виде это позволяет вам контролировать вывод команды в режиме реального времени , но он поставляется со следующими предостережениями :
Вам придется вручную активировать новые окна по отдельности, чтобы увидеть генерируемый вывод.
Вывод отображается только во время командыбежит;по завершении его окно закрывается автоматически, поэтому вы не можете проверить вывод после факта.
Чтобы обойти это, вам придется использовать что-то вроде Tee-Object
в PowerShellКомандлет также для захвата выходных данных в файле , который вызывающая сторона может позже проверить.
Это также единственный способ сделать вывод доступным программно , но только как текст .
Передача команд PowerShell в powershell.exe
через Start-Process
требует, чтобы вы передавали свои команды как строки (а не блоки скриптов), и имеет раздражающие требования синтаксического анализа, такие как необходимость экранирования "
символов как \"
(sic) - см. ниже.
И последнее, что не менее важно, использование Start-Process
также приводит к значительным накладным расходам на обработку (хотя с очень длительными командами, которые могут не иметь значения).
$startedAt = [datetime]::UtcNow
# Define the commands - of necessity - as *strings*.
# Note the unexpected need to escape the embedded " chars. as \"
$commands = '$n = 1; Start-Sleep $n; \"I ran for $n secs.\"',
'$n = 2; Start-Sleep $n; \"I ran for $n secs.\"',
'$n = 3; Start-Sleep $n; \"I ran for $n secs.\"'
# Use `Start-Process` to launch the commands asynchronously,
# in a new window each (Windows only).
# `-PassThru` passes an object representing the newly created process through.
$procs = $commands | ForEach-Object { Start-Process -PassThru powershell -Args '-c', $_ }
# Wait for all processes to exit.
$procs.WaitForExit()
"`nAll processes completed. Total runtime in secs.: $('{0:N2}' -f ([datetime]::UtcNow - $startedAt).TotalSeconds)"