Может ли Powershell запускать команды параллельно?

125

У меня есть сценарий PowerShell для выполнения пакетной обработки нескольких изображений, и я хотел бы выполнить некоторую параллельную обработку. Powershell, похоже, имеет некоторые параметры фоновой обработки, такие как start-job, wait-job и т. Д., Но единственным хорошим ресурсом, который я нашел для выполнения параллельной работы, было написание текста сценария и его запуск ( многопоточность PowerShell )

В идеале мне нужно что-то вроде parallel foreach в .NET 4.

Что-то довольно безобидное, вроде:

foreach-parallel -threads 4 ($file in (Get-ChildItem $dir))
{
   .. Do Work
}

Может, мне лучше просто перейти на С # ...

Алан Джексон
источник
tl; dr: receive-job (wait-job ($a = start-job { "heyo!" })); remove-job $a или $a = start-job { "heyo!" }; wait-job $a; receive-job $a; remove-job $aТакже обратите внимание, что если вы позвоните receive-jobдо завершения задания, вы можете вообще ничего не получить.
Эндрю
Также(get-job $a).jobstateinfo.state;
Эндрю

Ответы:

99

Вы можете выполнять параллельные задания в Powershell 2, используя фоновые задания . Ознакомьтесь с Start-Job и другими командлетами заданий.

# Loop through the server list
Get-Content "ServerList.txt" | %{

  # Define what each job does
  $ScriptBlock = {
    param($pipelinePassIn) 
    Test-Path "\\$pipelinePassIn\c`$\Something"
    Start-Sleep 60
  }

  # Execute the jobs in parallel
  Start-Job $ScriptBlock -ArgumentList $_
}

Get-Job

# Wait for it all to complete
While (Get-Job -State "Running")
{
  Start-Sleep 10
}

# Getting the information back from the jobs
Get-Job | Receive-Job
Стив Таунсенд
источник
3
Поэтому я пробовал это предложение несколько раз, но, похоже, мои переменные не расширяются правильно. Чтобы использовать тот же пример, когда эта строка выполняется: Test-Path "\\$_\c$\Something"я ожидаю, что она расширится $_до текущего элемента. Однако это не так. Вместо этого он возвращает пустое значение. Это происходит только изнутри блоков скрипта. Если я напишу это значение сразу после первого комментария, похоже, что оно работает правильно.
rjg
1
@likwid - звучит как отдельный вопрос для сайта
Стив Таунсенд
Как я могу просмотреть результат работы, которая выполняется в фоновом режиме?
SimpleGuy
@SimpleGuy - см. Здесь информацию о захвате вывода - stackoverflow.com/questions/15605095/… - не похоже, что вы можете надежно просмотреть это, пока не завершится фоновое задание.
Стив Таунсенд
@SteveTownsend Спасибо! На самом деле просмотр вывода на экран не так хорош. Поставляется с опозданием, поэтому мне не пригодится. Вместо этого я запустил процесс на новом терминале (оболочке), поэтому теперь каждый процесс работает на другом терминале, что дает представление о ходе выполнения намного лучше и чище.
SimpleGuy
98

Ответ Стива Таунсенда верен в теории, но не на практике, как указал @likwid. Мой исправленный код учитывает барьер контекста работы - по умолчанию ничто не преодолевает этот барьер! Таким образом, автоматическая $_переменная может использоваться в цикле, но не может использоваться непосредственно в блоке сценария, потому что она находится внутри отдельного контекста, созданного заданием.

Чтобы передать переменные из родительского контекста в дочерний контекст, используйте -ArgumentListпараметр on Start-Jobдля его отправки и используйте paramвнутри блока скрипта для его получения.

cls
# Send in two root directory names, one that exists and one that does not.
# Should then get a "True" and a "False" result out the end.
"temp", "foo" | %{

  $ScriptBlock = {
    # accept the loop variable across the job-context barrier
    param($name) 
    # Show the loop variable has made it through!
    Write-Host "[processing '$name' inside the job]"
    # Execute a command
    Test-Path "\$name"
    # Just wait for a bit...
    Start-Sleep 5
  }

  # Show the loop variable here is correct
  Write-Host "processing $_..."

  # pass the loop variable across the job-context barrier
  Start-Job $ScriptBlock -ArgumentList $_
}

# Wait for all to complete
While (Get-Job -State "Running") { Start-Sleep 2 }

# Display output from all jobs
Get-Job | Receive-Job

# Cleanup
Remove-Job *

(Обычно мне нравится давать ссылку на документацию PowerShell в качестве подтверждающего доказательства, но, увы, мой поиск оказался бесплодным. Если вы случайно знаете, где задокументировано разделение контекста, оставьте здесь комментарий, чтобы сообщить мне!)

Майкл Соренс
источник
Спасибо за этот ответ. Я пробовал использовать ваше решение, но мне не удалось заставить его полностью работать. Можете ли вы взглянуть на мой вопрос здесь: stackoverflow.com/questions/28509659/…
Дэвид говорит: «Восстановите Монику»
Кроме того, довольно просто вызвать отдельный файл сценария. Просто используйтеStart-Job -FilePath script.ps1 -ArgumentList $_
Чад Завистовски
Альтернативный подход состоит в том, чтобы выполнить предварительный проход генерации скрипта, на котором ничего не делается, кроме расширения переменных, а затем параллельно вызывать сгенерированные скрипты. У меня есть небольшой инструмент, который можно адаптировать для генерации скриптов, хотя он никогда не предназначался для поддержки генерации скриптов. Вы можете увидеть это здесь .
Уолтер Митти
Это работает. Но я не могу получить поток вывода в реальном времени из ScriptBlock. Вывод печатается только при возврате ScriptBlock.
vothaison
8

http://gallery.technet.microsoft.com/scriptcenter/Invoke-Async-Allows-you-to-83b0c9f0

Я создал invoke-async, который позволяет одновременно запускать несколько блоков скриптов / командлетов / функций. это отлично подходит для небольших заданий (сканирование подсети или wmi-запрос для сотен машин), потому что накладные расходы на создание пространства выполнения по сравнению со временем запуска start-job довольно значительны. Его можно использовать так.

со скриптблоком,

$sb = [scriptblock] {param($system) gwmi win32_operatingsystem -ComputerName $system | select csname,caption} 

$servers = Get-Content servers.txt 

$rtn = Invoke-Async -Set $server -SetParam system  -ScriptBlock $sb

просто командлет / функция

$servers = Get-Content servers.txt 

$rtn = Invoke-Async -Set $servers -SetParam computername -Params @{count=1} -Cmdlet Test-Connection -ThreadCount 50
jrich523
источник
8

В наши дни на этот вопрос так много ответов:

  1. вакансии (или threadjobs в PS 6/7 или модуле)
  2. запуск процесса
  3. Рабочие процессы
  4. Powershell api с другим пространством выполнения
  5. invoke-command с несколькими компьютерами, каждый из которых может быть localhost (должен быть администратором)
  6. несколько вкладок сеанса (пространство выполнения) в ISE или вкладки удаленного PowerShell ISE
  7. Powershell 7 предлагает foreach-object -parallelальтернативу # 4

Вот рабочие процессы с буквально foreach -parallel:

workflow work {
  foreach -parallel ($i in 1..3) { 
    sleep 5 
    "$i done" 
  }
}

work

3 done
1 done
2 done

Или рабочий процесс с параллельным блоком:

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

Вот пример api с runspaces:

$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() # run in background
$a.EndInvoke($r1); $b.EndInvoke($r2); $c.EndInvoke($r3) # wait
($a,$b,$c).streams.error # check for errors
($a,$b,$c).dispose() # clean

a done
b done
c done
js2010
источник
7

Фоновые задания дороги в настройке и не подлежат повторному использованию. У PowerShell MVP Ойсин Грехан есть хороший пример многопоточности PowerShell.

(25.10.2010 сайт не работает, но доступен через веб-архив).

Я использовал адаптированный сценарий Oisin для использования в процедуре загрузки данных здесь:

http://rsdd.codeplex.com/SourceControl/changeset/view/a6cd657ea2be#Invoke-RSDDThreaded.ps1

Чад Миллер
источник
Ссылка гниль установила для этого ответа
Люк
4

Чтобы завершить предыдущие ответы, вы также можете использовать Wait-Jobдля ожидания завершения всех заданий:

For ($i=1; $i -le 3; $i++) {
    $ScriptBlock = {
        Param (
            [string] [Parameter(Mandatory=$true)] $increment
        )

        Write-Host $increment
    }

    Start-Job $ScriptBlock -ArgumentList $i
}

Get-Job | Wait-Job | Receive-Job
Томас
источник
0

В Powershell 7 вы можете использовать ForEach-Object -Parallel

$Message = "Output:"
Get-ChildItem $dir | ForEach-Object -Parallel {
    "$using:Message $_"
} -ThrottleLimit 4
izharsa
источник
0

Если вы используете новейшую кросс-платформенную оболочку PowerShell (которую, кстати, следует) https://github.com/powershell/powershell#get-powershell , вы можете добавить одиночный &запуск параллельных скриптов. (Использование; для последовательного запуска)

В моем случае мне нужно было запустить 2 сценария npm параллельно: npm run hotReload & npm run dev


Вы также можете настроить npm для использования powershellв его скриптах (по умолчанию он используется cmdв Windows).

Запустите из корневой папки проекта: npm config set script-shell pwsh --userconfig ./.npmrc а затем используйте одну команду сценария npm:npm run start

"start":"npm run hotReload & npm run dev"
JerryGoyal
источник