136 votes

Powershell peut-il exécuter des commandes en parallèle ?

J'ai un script powershell pour effectuer un traitement par lots sur un groupe d'images et j'aimerais effectuer un traitement parallèle. Powershell semble avoir quelques options de traitement en arrière-plan telles que start-job, wait-job, etc, mais la seule bonne ressource que j'ai trouvée pour effectuer un travail parallèle était d'écrire le texte d'un script et de les exécuter ( PowerShell Multithreading )

Idéalement, j'aimerais quelque chose qui ressemble à la méthode foreach parallèle de .net 4.

Quelque chose d'assez banal comme :

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

Peut-être que je ferais mieux de descendre en C#...

0 votes

tl;dr : receive-job (wait-job ($a = start-job { "heyo!" })); remove-job $a o $a = start-job { "heyo!" }; wait-job $a; receive-job $a; remove-job $a Notez également que si vous appelez receive-job avant que le travail soit terminé, vous pourriez ne rien recevoir du tout.

0 votes

Aussi (get-job $a).jobstateinfo.state;

107voto

Steve Townsend Points 36948

Vous pouvez exécuter des travaux parallèles dans Powershell 2 en utilisant Emplois de base . Vérifier Start-Job et les autres cmdlets de travail.

# 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 votes

J'ai essayé cette suggestion plusieurs fois, mais il semble que mes variables ne soient pas développées correctement. Pour reprendre le même exemple, lorsque cette ligne s'exécute : Test-Path "\\$_\c$\Something" Je m'attendrais à ce qu'il se développe $_ dans l'élément actuel. Cependant, il ne le fait pas. Au lieu de cela, il renvoie une valeur vide. Cela semble se produire uniquement à l'intérieur des blocs script. Si j'écris cette valeur immédiatement après le premier commentaire, cela semble fonctionner correctement.

1 votes

@likwid - On dirait une question distincte pour le site.

0 votes

Comment puis-je visualiser le résultat du travail qui s'exécute en arrière-plan ?

102voto

Michael Sorens Points 9637

La réponse de Steve Townsend est correcte en théorie mais pas en pratique comme l'a souligné @likwid. Mon code révisé tient compte de la barrière de contexte de travail --rien ne franchit cette barrière par défaut ! Le système automatique $_ peut donc être utilisée dans la boucle mais ne peut pas être utilisée directement dans le bloc script car elle se trouve dans un contexte séparé créé par le job.

Pour passer des variables du contexte parent au contexte enfant, utilisez la fonction -ArgumentList le paramètre sur Start-Job pour l'envoyer et utiliser param à l'intérieur du bloc script pour le recevoir.

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 *

(J'aime généralement fournir une référence à la documentation PowerShell comme preuve à l'appui mais, hélas, ma recherche a été infructueuse. Si vous savez où la séparation de contexte est documentée, postez un commentaire ici pour me le faire savoir).

0 votes

Merci pour cette réponse. J'ai essayé d'utiliser votre solution, mais je n'ai pas réussi à la faire fonctionner complètement. Pouvez-vous jeter un coup d'œil à ma question ici : stackoverflow.com/questions/28509659/

0 votes

Alternativement, il est assez facile d'invoquer un fichier script séparé. Il suffit d'utiliser Start-Job -FilePath script.ps1 -ArgumentList $_

0 votes

Une approche alternative consiste à faire une passe préliminaire de génération de script, où rien n'est fait à part l'expansion des variables, et ensuite invoquer les scripts générés en parallèle. J'ai un petit outil qui pourrait être adapté à la génération de script, bien qu'il n'ait jamais été conçu pour supporter la génération de script. Vous pouvez le voir aquí .

22voto

js2010 Points 823

Il y a tellement de réponses à cela de nos jours :

  1. jobs (ou threadjobs dans PS 6/7 ou le module pour PS 5)
  2. Démarrer le processus
  3. flux de travail (PS 5 uniquement)
  4. powershell api avec un autre espace d'exécution
  5. invoke-command avec plusieurs ordinateurs, qui peuvent tous être localhost (il faut être admin)
  6. onglets de sessions multiples (runpace) dans l'ISE, ou onglets de l'ISE de powershell à distance
  7. Powershell 7 dispose d'une foreach-object -parallel comme alternative au n°4

Voici des flux de travail avec littéralement un foreach -parallèle :

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

work

3 done
1 done
2 done

Ou un flux de travail avec un bloc parallèle :

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

Voici un exemple d'api avec 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

2 votes

Il est intéressant de noter que les workflows ne sont plus supportés depuis PowerShell 6, c'est-à-dire depuis que PowerShell est passé à .NET Core. Ils sont construits à partir de Windows Workflow Foundation, qui n'est pas disponible dans .NET Core. Il y a bien la remarque "PS 5 seulement", mais elle est facile à ignorer.

1 votes

Start-Process crée généralement une nouvelle fenêtre. Avec -NoNewWindow, le processus obtient un accès direct à la console actuelle. C'est différent des jobs et de ForEach-Object -Parallel, qui traitent d'une manière ou d'une autre la sortie.

8voto

jrich523 Points 121

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

j'ai créé un invoke-async qui vous permet d'exécuter plusieurs script blocs/cmdlets/fonctions en même temps. c'est génial pour les petits travaux (subnet scan ou wmi query contre 100's de machines) parce que l'overhead pour créer un runpace vs le temps de démarrage de start-job est assez drastique. Il peut être utilisé comme suit.

avec 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

juste cmdlet/fonction

$servers = Get-Content servers.txt 

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

7voto

Chad Miller Points 6207

Les tâches d'arrière-plan sont coûteuses à mettre en place et ne sont pas réutilisables. Le MVP PowerShell Oisin Grehan a créé un bon exemple de PowerShell multithreading.

(Le site du 25/10/2010 est hors service, mais accessible via l'archive Web).

J'ai utilisé une adaptation du script d'Oisin pour l'utiliser dans une routine de chargement de données ici :

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

0 votes

La pourriture du lien s'est installée pour cette réponse

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X