Прежде чем запускать следующие команды, дождитесь завершения нескольких одновременных команд powershell в других сеансах. - PullRequest
2 голосов
/ 15 июня 2019

Я пытаюсь заставить главный сценарий PowerShell выполнить следующее:

  1. Выполнить команды в 3 других сеансах PowerShell (все они идут в течение ~ 1 часа - я бы хотел, чтобы они выполнялись одновременночтобы все выполняемые задания могли выполняться одновременно)
  2. Дождаться всех 3 других сеансов PowerShell для завершения
  3. Продолжить, оставшиеся команды вначальное окно powershell

Чрезвычайно простой пример

Мой реальный пример использования аналогичен следующему, за исключением того, что времена всегда меняются, ECHO "hi" должно происходить только один раз для всех остальных (3)Команды завершены (в этом случае мы знаем, что они займут 10000 секунд, но в моем реальном случае использования это сильно варьируется).Также обратите внимание, что не ясно, какая из 3 команд будет длиться дольше каждого раза.

start powershell { TIMEOUT 2000 }
start powershell { TIMEOUT 3000 }
start powershell { TIMEOUT 10000 }
ECHO "hi"

Я вижу ( здесь ), что я могу поставить & перед командой, чтобы сказать powershell ждать, пока она не завершится, прежде чем перейти к последующим командам.Однако я не знаю, как это сделать с 3 одновременными командами

Ответы [ 2 ]

3 голосов
/ 15 июня 2019

Вы действительно ищете 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)"
1 голос
/ 08 июля 2019

Простой ответ на вопрос, используя задания.

start-job { sleep 2000 }
start-job { sleep 3000 }
start-job { sleep 10000 }
get-job | wait-job
echo hi

Вот другой способ. Начните с псевдонима для start-process. Вы могли бы использовать тайм-аут вместо сна. Время ожидания три раза выглядит довольно круто, но это может испортить некоторые результаты.

$a = start -NoNewWindow powershell {timeout 10; 'a done'} -PassThru
$b = start -NoNewWindow powershell {timeout 10; 'b done'} -PassThru
$c = start -NoNewWindow powershell {timeout 10; 'c done'} -PassThru
$a,$b,$c | wait-process
'hi'

b done
c done

a done
hi

Вот попытка рабочего процесса.

function sleepfor($time) { sleep $time; "sleepfor $time done"}

workflow work {
  parallel {
    sleepfor 3
    sleepfor 2
    sleepfor 1
  }
  'hi'
}

work 

sleepfor 1 done
sleepfor 2 done
sleepfor 3 done
hi

Или просто:

function sleepfor($time) { sleep $time; "sleepfor $time done"}

workflow work2 {
  foreach -parallel ($i in 1..3) { sleepfor 10 }
  'hi'
}

work2 # runs in about 13 seconds

sleepfor 10 done
sleepfor 10 done
sleepfor 10 done
hi

Попытка API с 3 пробелами:

$a =  [PowerShell]::Create().AddScript{sleep 5;'a done'}
$b =  [PowerShell]::Create().AddScript{sleep 5;'b done'}
$c =  [PowerShell]::Create().AddScript{sleep 5;'c done'}
$r1,$r2,$r3 = ($a,$b,$c).begininvoke()
$a.EndInvoke($r1); $b.EndInvoke($r2); $c.EndInvoke($r3)
($a,$b,$c).dispose()

a done
b done
c done

Команда удаленного вызова:

invoke-command localhost,localhost,localhost { sleep 5; 'done' }

done
done
done
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...