我正在尝试获取Powershell主脚本来执行以下操作:
我的实际用例与以下类似,只是时间总是变化,ECHO "hi"
应该仅在所有其他(3)命令完成后才发生(在这种情况下,我们将花费10000秒,但在我的实际用例中,这相差很大)。还要注意,尚不清楚这3个命令中的哪个命令每次花费的时间最长。
start powershell { TIMEOUT 2000 }
start powershell { TIMEOUT 3000 }
start powershell { TIMEOUT 10000 }
ECHO "hi"
我看到(version)可以在命令前放置一个&
,以便告诉Powershell在完成后续命令之前要等到它完成为止。但是,我不知道如何使用3个同时执行的命令
答案 0 :(得分:3)
正如background jobs所建议的那样,您确实在寻找 Powershell Lee Daily 。
但是,任务比较繁琐,因为每个任务都在自己的进程 中运行,这会带来大量开销。
ThreadJob
module提供了基于线程 的轻量级替代方案。
它随PowerShell Core 一起提供,并且在Windows中可以按需安装PowerShell,例如,
Install-Module ThreadJob -Scope CurrentUser
。
您只需调用Start-ThreadJob
而不是Start-Job
,然后使用标准的*-Job
cmdlet 管理这样的线程作业-与您管理常规的后台工作。
这是一个例子:
$startedAt = [datetime]::UtcNow
# Define the commands to run as [thread] jobs.
$commands = { $n = 2; Start-Sleep $n; "I ran for $n secs." },
{ $n = 3; Start-Sleep $n; "I ran for $n secs." },
{ $n = 10; Start-Sleep $n; "I ran for $n secs." }
# Start the (thread) jobs.
# You could use `Start-Job` here, but that would be more resource-intensive
# and make the script run considerably longer.
$jobs = $commands | Foreach-Object { Start-ThreadJob $_ }
# Wait until all jobs have completed, passing their output through as it
# is received, and automatically clean up afterwards.
$jobs | Receive-Job -Wait -AutoRemoveJob
"All jobs completed. Total runtime in secs.: $(([datetime]::UtcNow - $startedAt).TotalSeconds)"
上面的结果类似于以下内容;请注意,在可用时报告了单个命令的输出,但是直到所有命令完成,调用脚本的执行才会继续:
I ran for 2 secs.
I ran for 3 secs.
I ran for 10 secs.
All jobs completed. Total runtime in secs.: 10.2504931
注意:在这种简单情况下,很明显哪个输出来自哪个命令,但是更典型地,各种作业的输出将运行不可预测的交错,这使得它难以解释输出-请参阅下一部分以获取解决方案。
如您所见,在后台为基于线程的并行执行引入的开销很小-总体执行只花了10秒多一点的时间,这是3个命令中运行时间最长的时间。
如果您改用基于流程的Start-Job
,则总体执行时间可能如下所示,这表明引入了相当大的开销,尤其是第一次在会话中运行后台作业时:>
All jobs completed. Total runtime in secs.: 18.7502717
也就是说,至少在会话的第一次调用中,后台并行执行的好处被否定了-在这种情况下,执行所需的时间比顺序执行的时间长。
尽管在同一会话中后续的基于进程的后台作业运行速度更快,但开销仍然明显高于基于线程的作业。
如果要显示后台命令每个命令的输出,则需要分别收集输出。
注意:在控制台窗口(终端)中,这要求您等待所有命令完成后才能显示输出(因为无法通过就地更新同时显示多个输出流,至少使用常规输出命令。)
$startedAt = [datetime]::UtcNow
$commands = { $n = 1; Start-Sleep $n; "I ran for $n secs." },
{ $n = 2; Start-Sleep $n; "I ran for $n secs." },
{ $n = 3; Start-Sleep $n; "I ran for $n secs." }
$jobs = $commands | Foreach-Object { Start-ThreadJob $_ }
# Wait until all jobs have completed.
$null = Wait-Job $jobs
# Collect the output individually for each job and print it.
foreach ($job in $jobs) {
"`n--- Output from {$($job.Command)}:"
Receive-Job $job
}
"`nAll jobs completed. Total runtime in secs.: $('{0:N2}' -f ([datetime]::UtcNow - $startedAt).TotalSeconds)"
上面将打印如下内容:
--- Output from { $n = 1; Start-Sleep $n; "I ran for $n secs." }:
I ran for 1 secs.
--- Output from { $n = 2; Start-Sleep $n; "I ran for $n secs." }:
I ran for 2 secs.
--- Output from { $n = 3; Start-Sleep $n; "I ran for $n secs." }:
I ran for 3 secs.
All jobs completed. Total runtime in secs.: 3.09
Start-Process
在单独的窗口中运行命令在Windows上,您可以使用Start-Process
(别名为start
)在新窗口中运行命令,该窗口也是异步默认情况下,即串行启动命令确实并行运行 。
在有限的形式中,这允许您实时监控 特定于命令的输出,但随附以下 cavesat :
您必须单独手动激活新窗口才能看到正在生成的输出。
仅当命令运行时,输出才可见;完成后,其窗口将自动关闭,因此您无法在事后检查输出。
要解决此问题,您必须在PowerShell cmdlet中使用诸如Tee-Object
之类的内容,以便还捕获 file 中的输出,调用者以后可以检查该文件
这也是通过以编程方式提供输出的唯一方法,尽管仅以 text 形式出现。
通过powershell.exe
将PowerShell命令传递到Start-Process
要求您以 strings (而不是脚本块)形式传递命令,并且具有令人讨厌的解析要求,例如需要转义"
个字符。如\"
(原文如此)-参见下文。
最后且并非最不重要的一点是,使用Start-Process
还会带来大量的处理开销(尽管对于运行时间很长的命令来说可能并不重要)。
$startedAt = [datetime]::UtcNow
# Define the commands - of necessity - as *strings*.
# Note the unexpected need to escape the embedded " chars. as \"
$commands = '$n = 1; Start-Sleep $n; \"I ran for $n secs.\"',
'$n = 2; Start-Sleep $n; \"I ran for $n secs.\"',
'$n = 3; Start-Sleep $n; \"I ran for $n secs.\"'
# Use `Start-Process` to launch the commands asynchronously,
# in a new window each (Windows only).
# `-PassThru` passes an object representing the newly created process through.
$procs = $commands | ForEach-Object { Start-Process -PassThru powershell -Args '-c', $_ }
# Wait for all processes to exit.
$procs.WaitForExit()
"`nAll processes completed. Total runtime in secs.: $('{0:N2}' -f ([datetime]::UtcNow - $startedAt).TotalSeconds)"
答案 1 :(得分:1)
使用作业对问题的简单回答。
start-job { sleep 2000 }
start-job { sleep 3000 }
start-job { sleep 10000 }
get-job | wait-job
echo hi
这是另一种方式。开始是启动过程的别名。您可以使用超时而不是睡眠。实际运行三遍超时看起来确实很酷,但是会弄乱一些输出。
$a = start -NoNewWindow powershell {timeout 10; 'a done'} -PassThru
$b = start -NoNewWindow powershell {timeout 10; 'b done'} -PassThru
$c = start -NoNewWindow powershell {timeout 10; 'c done'} -PassThru
$a,$b,$c | wait-process
'hi'
b done
c done
a done
hi
这里是工作流程的尝试。
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
或者只是:
function sleepfor($time) { sleep $time; "sleepfor $time done"}
workflow work2 {
foreach -parallel ($i in 1..3) { sleepfor 10 }
'hi'
}
work2 # runs in about 13 seconds
sleepfor 10 done
sleepfor 10 done
sleepfor 10 done
hi
Api尝试3个运行空间:
$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()
$a.EndInvoke($r1); $b.EndInvoke($r2); $c.EndInvoke($r3)
($a,$b,$c).dispose()
a done
b done
c done
远程调用命令:
invoke-command localhost,localhost,localhost { sleep 5; 'done' }
done
done
done