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


125

У мене є скрипт повноважень, щоб зробити деяку пакетну обробку на купі зображень, і я хотів би зробити паралельну обробку. У Powershell, здається, є деякі варіанти фонової обробки, такі як старт-робота, робота-очікування тощо, але єдиний хороший ресурс, який я знайшов для виконання паралельних робіт, - це написання тексту сценарію та його виконання ( PowerShell Multithreading )

В ідеалі, я хотів би, щоб щось подібне до паралельного проповідування в .net 4.

Щось таке, схоже, начебто:

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

Можливо, мені буде краще просто перейти до c # ...


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, використовуючи Background Jobs . Ознайомтеся з командлетами 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

Я створив виклик-async, який дозволяє одночасно запускати кілька блоків скриптів / cmdlets / функцій. це чудово підходить для невеликих робочих місць (сканування підмережі або WM-запит проти 100-ти машин), оскільки накладні витрати на створення пробігу та часу запуску роботи-початківця досить драматичні. Це можна використовувати так.

за допомогою сценарію,

$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

просто cmdlet / функція

$servers = Get-Content servers.txt 

$rtn = Invoke-Async -Set $servers -SetParam computername -Params @{count=1} -Cmdlet Test-Connection -ThreadCount 50

8

На сьогодні існує стільки відповідей на це:

  1. завдання (або виконання ниток в PS 6/7 або модулі)
  2. старт-процес
  3. робочі процеси
  4. powerhell api з іншим пробілом
  5. invoke-command з декількома комп’ютерами, які можуть бути локальними (повинні бути адміністратором)
  6. вкладки декількох сеансів (пробігу) на ISE або віддалені вкладки ISE на вкладці
  7. Powershell 7 має foreach-object -parallelальтернативу №4

Ось робочі процеси з буквально передбаченням - паралельно:

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 із прикладом пробігу:

$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

7

Завдання фонів дорогі для налаштування і не підлягають багаторазовому використанню. PowerShell MVP Oisin Grehan є хорошим прикладом 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

0

Якщо ви використовуєте останню межплатформенную панель повноважень (яку вам слід переробити) 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"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.