Powershell可以并行运行命令吗?


125

我有一个powershell脚本来对一堆图像进行一些批处理,我想进行一些并行处理。Powershell似乎有一些后台处理选项,例如start-job,wait-job等,但是我发现进行并行工作的唯一好资源是编写脚本文本并运行它们(PowerShell Multithreading

理想情况下,我想要类似于.net 4中的并行foreach的东西。

看起来很像的东西:

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;
安德鲁(Andrew)

Answers:


99

您可以使用Background Jobs在Powershell 2中执行并行作业。签出Start-Job和其他作业cmdlet。

# 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-听起来像网站的一个单独问题
Steve Townsend

如何查看在后台运行的作业的输出?
SimpleGuy

@SimpleGuy -在这里看到的输出采集信息- stackoverflow.com/questions/15605095/... -似乎并不像您可以可靠地查看,直到后台作业完成。
史蒂夫·汤森

@SteveTownsend谢谢!实际上,在屏幕上查看输出效果不是很好。带有延迟,所以对我没有用。相反,我在新的终端(外壳)上启动了一个进程,因此现在每个进程都在不同的终端上运行,这使进度视图变得更好,更干净。
SimpleGuy

98

史蒂夫·汤森(Steve Townsend)的答案在理论上是正确的,但在实践中却不如@likwid指出的那样。我修改后的代码考虑到了工作上下文障碍-默认情况下,没有任何东西可以跨越该障碍!$_因此,可以在循环中使用自动变量,但是不能在脚本块中直接使用自动变量,因为它位于作业创建的单独上下文中。

要将变量从父上下文传递到子上下文,请使用-ArgumentListon参数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 $_
Chad Zawistowski

另一种方法是执行脚本生成的初步过程,除了变量扩展之外,什么都不做,然后并行调用生成的脚本。我有一个可能适合脚本生成的小工具,尽管它从来没有支持脚本生成。你可以在这里看到它。
沃尔特·米蒂

这可行。但是我无法从ScriptBlock获得实时提​​要输出流。仅在ScriptBlock返回时才打印输出。
vothaison

8

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

我创建了一个invoke-async,它允许您同时运行多个脚本块/ cmdlet /功能。这对于小型作业(子网扫描或针对100台机器的wmi查询)非常有用,因为创建运行空间与开始作业的启动时间的开销相当大。可以像这样使用。

使用脚本块

$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或模块中的threadjob)
  2. 开始过程
  3. 工作流程
  4. Powershell API与另一个运行空间
  5. 具有多台计算机的invoke-command都可以是localhost(必须是admin)
  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:

$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


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

如果您正在使用最新的跨平台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"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.