Bisakah Powershell Menjalankan Perintah secara Paralel?

125

Saya memiliki skrip PowerShell untuk melakukan pemrosesan batch pada sekumpulan gambar dan saya ingin melakukan pemrosesan paralel. Powershell tampaknya memiliki beberapa opsi pemrosesan latar belakang seperti start-job, wait-job, dll, tetapi satu-satunya sumber daya bagus yang saya temukan untuk melakukan pekerjaan paralel adalah menulis teks skrip dan menjalankannya ( PowerShell Multithreading )

Idealnya, saya ingin sesuatu yang mirip dengan parallel foreach di .net 4.

Sesuatu yang sangat tidak terlihat seperti:

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

Mungkin lebih baik aku turun ke c # ...

Alan Jackson
sumber
tl; dr: receive-job (wait-job ($a = start-job { "heyo!" })); remove-job $a atau $a = start-job { "heyo!" }; wait-job $a; receive-job $a; remove-job $aPerhatikan juga bahwa jika Anda menelepon receive-jobsebelum pekerjaan selesai, Anda mungkin tidak mendapatkan apa-apa.
Andrew
Juga(get-job $a).jobstateinfo.state;
Andrew

Jawaban:

99

Anda dapat menjalankan pekerjaan paralel di Powershell 2 menggunakan Pekerjaan Latar Belakang . Lihat Start-Job dan cmdlet pekerjaan lainnya.

# 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
Steve Townsend
sumber
3
Jadi saya mencoba saran ini beberapa kali, tetapi tampaknya variabel saya tidak berkembang dengan benar. Untuk menggunakan contoh yang sama, saat baris ini dijalankan: Test-Path "\\$_\c$\Something"Saya mengharapkannya untuk diperluas $_ke item saat ini. Namun, ternyata tidak. Sebaliknya itu mengembalikan nilai kosong. Ini sepertinya hanya terjadi dari dalam blok skrip. Jika saya menulis nilai itu segera setelah komentar pertama, tampaknya berfungsi dengan benar.
rjg
1
@likwid - terdengar seperti pertanyaan terpisah untuk situs ini
Steve Townsend
Bagaimana cara melihat keluaran dari pekerjaan yang berjalan di latar belakang?
SimpleGuy
@SimpleGuy - lihat di sini untuk info tentang pengambilan keluaran - stackoverflow.com/questions/15605095/… - sepertinya Anda tidak dapat melihat ini dengan andal sampai pekerjaan latar belakang selesai.
Steve Townsend
@SteveTownsend Terima kasih! Sebenarnya melihat output tidak begitu bagus di layar. Datang dengan penundaan, jadi tidak berguna bagi saya. Sebagai gantinya saya memulai proses pada terminal baru (shell), jadi sekarang setiap proses berjalan pada terminal yang berbeda yang memberikan pandangan kemajuan yang jauh lebih baik dan lebih bersih.
SimpleGuy
98

Jawaban dari Steve Townsend benar secara teori tetapi tidak dalam praktek seperti yang dikatakan @likwid. Kode revisi saya memperhitungkan penghalang konteks pekerjaan --tidak ada yang melewati penghalang itu secara default! $_Variabel otomatis dengan demikian dapat digunakan dalam loop tetapi tidak dapat digunakan secara langsung di dalam blok skrip karena berada di dalam konteks terpisah yang dibuat oleh pekerjaan.

Untuk meneruskan variabel dari konteks induk ke konteks anak, gunakan -ArgumentListparameter pada Start-Jobuntuk mengirimnya dan gunakan paramdi dalam blok skrip untuk menerimanya.

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 *

(Saya biasanya suka memberikan referensi ke dokumentasi PowerShell sebagai bukti pendukung tetapi, sayangnya, pencarian saya tidak membuahkan hasil. Jika Anda kebetulan mengetahui di mana pemisahan konteks didokumentasikan, kirim komentar di sini untuk memberi tahu saya!)

Michael Sorens
sumber
Terima kasih atas jawaban ini. Saya mencoba menggunakan solusi Anda, tetapi saya tidak dapat membuatnya berfungsi sepenuhnya. Dapatkah Anda melihat pertanyaan saya di sini: stackoverflow.com/questions/28509659/…
David mengatakan Reinstate Monica
Atau, cukup mudah untuk menjalankan file skrip terpisah. Cukup gunakanStart-Job -FilePath script.ps1 -ArgumentList $_
Chad Zawistowski
Pendekatan alternatif adalah dengan melakukan langkah awal pembuatan skrip, di mana tidak ada yang dilakukan kecuali perluasan variabel, dan kemudian memanggil skrip yang dihasilkan secara paralel. Saya memiliki alat kecil yang dapat disesuaikan dengan pembuatan skrip, meskipun itu tidak pernah dimaksudkan untuk mendukung pembuatan skrip. Anda bisa melihatnya di sini .
Walter Mitty
Ini bekerja. Tapi saya tidak bisa mendapatkan aliran keluaran umpan langsung dari ScriptBlock. Output hanya dicetak ketika ScriptBlock kembali.
vothaison
8

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

saya membuat invoke-async yang memungkinkan Anda menjalankan beberapa blok skrip / cmdlet / fungsi pada saat yang bersamaan. ini bagus untuk pekerjaan kecil (pemindaian subnet atau kueri wmi terhadap 100-an mesin) karena overhead untuk membuat runspace vs waktu startup dari pekerjaan mulai cukup drastis. Itu bisa digunakan seperti itu.

dengan scriptblock,

$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

hanya cmdlet / function

$servers = Get-Content servers.txt 

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

Ada begitu banyak jawaban untuk saat ini:

  1. pekerjaan (atau threadjobs di PS 6/7 atau modul)
  2. mulai-proses
  3. alur kerja
  4. PowerShell api dengan runspace lain
  5. perintah-panggil dengan banyak komputer, yang semuanya bisa menjadi host lokal (harus admin)
  6. beberapa tab sesi (runspace) di ISE, atau tab ISE PowerShell jarak jauh
  7. Powershell 7 memiliki foreach-object -parallelalternatif untuk # 4

Berikut alur kerja dengan foreach -parallel:

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

work

3 done
1 done
2 done

Atau alur kerja dengan blok paralel:

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

Berikut adalah contoh api dengan 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
sumber
4

Untuk menyelesaikan jawaban sebelumnya, Anda juga dapat menggunakan Wait-Jobuntuk menunggu semua pekerjaan selesai:

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
Thomas
sumber
0

Di Powershell 7 Anda dapat menggunakan ForEach -Object -Parallel

$Message = "Output:"
Get-ChildItem $dir | ForEach-Object -Parallel {
    "$using:Message $_"
} -ThrottleLimit 4
izharsa
sumber
0

Jika Anda menggunakan PowerShell lintas platform terbaru (yang seharusnya Anda btw) https://github.com/powershell/powershell#get-powershell , Anda dapat menambahkan single &untuk menjalankan skrip paralel. (Menggunakan; untuk menjalankan secara berurutan)

Dalam kasus saya, saya perlu menjalankan skrip 2 npm secara paralel: npm run hotReload & npm run dev


Anda juga dapat mengatur npm untuk digunakan powershelluntuk skripnya (secara default digunakan cmddi windows).

Jalankan dari folder root proyek: npm config set script-shell pwsh --userconfig ./.npmrc lalu gunakan perintah skrip npm tunggal:npm run start

"start":"npm run hotReload & npm run dev"
JerryGoyal
sumber