访问当前 PowerShell 实例
Access current PowerShell instance
我有一个 PowerShell 脚本,我想在其中创建一个后台线程并与我的主线程动态交换数据。这个想法是使用信息流,因为它可以轻松处理各种对象。
通常我会像下面这样将 PowerShell 对象赋予自身:
$Code =
{
Param($Me)
#Here I can use $Me.Streams.Information to exchange data any time,
#for example to feed my thread with more work to do on the fly
$ResultData = [System.Object[]]::new(0)
$WorkCounter = 0
$Finished = $false
while (-not $Finished)
{
while ($Me.Streams.Information.Count -eq $WorkCounter)
{
#Wait for data to be added to the information stream
Sleep -MilliSeconds 10
}
$InputData = $Me.Streams.Information[-1].MessageData
if ($InputData -eq "FINISHED")
{
$Finished = $true
}
else
{
<# Do some stuff with the $InputData #>
$ResultData += $ProgressedInputData
}
$WorkCounter++
}
Write-Information $ResultData
}
$PS = [PowerShell]::Create()
$PS.AddScript($Code) | Out-Null
$PS.AddArgument($PS) | Out-Null #Hand the PS to itself to make the streams accessible inside the thread
$Handle = $PS.BeginInvoke() | Out-Null
for ($i = 0; $i -lt 10; $i++)
{
$PS.Streams.Information.Add([System.Management.Automation.InformationRecord]::new($i, ""))
#I just gave my background thread some stuff to do without the need to instantiate a new one again
#Now this thread can do some work too...
}
$PS.Streams.Information.Add([System.Management.Automation.InformationRecord]::new("FINISHED", ""))
$Handle.AsyncWaitHandle.WaitOne() #Wait for my background thread to finish all its work
$SomeReturnValue = $PS.Streams.Information[-1].MessageData
我的实际问题是:是否有可能访问当前的 PowerShell 实例而无需像我对 $PS.AddArgument($PS)?
您不需要滥用 PowerShell.Streams
进行 two-way 通信 - PowerShell 已经有一个在两个运行空间之间桥接会话状态的工具,一个 so-called 会话状态代理!
让我们从稍微重写您的 $code
块开始:
$code = {
while($config['Enabled']){
$inputData = $null
if($queue.TryDequeue([ref]$inputData)) {
#process input data
Write-Information $inputData
}
else {
Start-Sleep -Milliseconds 50
}
}
}
请注意,我使用了两个变量,$config
和 $queue
,尽管我实际上没有参数化或以其他方式定义它们,但我们仍在使用信息流进行通信输出(你也可以使用标准输出)。
现在我们只需要创建两个可以绑定到这些变量的对象。对于 thread-safety,我们将使用:
- A
ConcurrentQueue[psobject]
输入数据
- A thread-synchronized
[hashtable]
配置数据
# Create PowerShell instance like before
$PS = [powershell]::Create()
# Create the thread-safe collections we'll be using to communicate
$queue = [System.Collections.Concurrent.ConcurrentQueue[psobject]]::new()
$config = [hashtable]::Synchronized(@{
Enabled = $true
})
# Now make those variable references available in the runspace where the backgroun code will be running
$ps.Runspace.SessionStateProxy.PSVariable.Set('queue', $queue)
$ps.Runspace.SessionStateProxy.PSVariable.Set('config', $config)
有了用于交换输入和配置数据的工具,您现在可以调用后台作业并根据通过 $queue
提供的输入观察它的行为(我强烈建议将以下语句输入到交互式提示,稍微玩一下):
# Invoke background code
$asyncHandle = $PS.BeginInvoke()
# Try adding some data to the queue
$queue.TryAdd([pscustomobject]@{ Property = "Value 123"})
# Wait for a bit
Start-Sleep -Milliseconds 100
# Observe that the queue has been emptied by the background code
Write-Host "Queue is empty: $($queue.IsEmpty)"
# Observe that the background code actually processed (and output) the data
$ps.Streams.Information
让我提供 , based on the ThreadJob
module's Start-ThreadJob
cmdlet, which ships with PowerShell (Core) v6+ 的替代方案,在 Windows PowerShell 中可以按需安装(例如,Install-Module ThreadJob -Scope CurrentUser
)
顾名思义,它提供基于 线程 的后台操作,作为 child-process 的更快 lighter-weight 替代方案 由 Start-Job
(see 为并列创建的基于 的后台作业。
因此,它 更友好,higher-level 替代通过 PowerShell SDK 管理多线程(运行空间) ,允许您使用常用的 job-management cmdlet 与后台线程交互。
一个简单的例子:
# Create a synchronized (thread-safe) queue.
$threadSafeQueue = [System.Collections.Concurrent.ConcurrentQueue[string]]::new()
# Start a thread job that keeps processing elements in the queue
# indefinitely, sleeping a little between checks for new elements.
# A special element value is used to signal that processing should end.
$jb = Start-ThreadJob {
$q = $using:threadSafeQueue # get a reference to the thread-safe queue.
$element = $null # variable to receive queue elements
while ($true) {
# Process all elements currently in the queue.
while ($q.TryDequeue([ref] $element)) {
# Check for the signal to quit, by convention a single NUL char. here.
if ("`0" -eq $element) { 'Quitting...'; return }
# Process the element at hand.
# In this example, echo the dequeued element enclosed in "[...]"
'[{0}]' -f $element
}
# Queue is (now) empty, sleep a little before checking for new elements.
Start-Sleep -MilliSeconds 100
}
}
# Populate the queue with the numbers from 1 to 10.
1..10 | ForEach-Object {
$threadSafeQueue.Enqueue($_) # This triggers activity in the background thread.
# Retrieve available output from the thread job.
$jb | Receive-Job
}
# Send the quit signal, retrieve remaining output and delete the job.
$threadSafeQueue.Enqueue("`0")
$jb | Receive-Job -Wait -AutoRemoveJob
输出:
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
[10]
Quitting...
另请参阅:
- PowerShell (Core) v7+
Foreach-Object -Parallel
功能,它同样使用线程并行处理 管道输入 。
我有一个 PowerShell 脚本,我想在其中创建一个后台线程并与我的主线程动态交换数据。这个想法是使用信息流,因为它可以轻松处理各种对象。
通常我会像下面这样将 PowerShell 对象赋予自身:
$Code =
{
Param($Me)
#Here I can use $Me.Streams.Information to exchange data any time,
#for example to feed my thread with more work to do on the fly
$ResultData = [System.Object[]]::new(0)
$WorkCounter = 0
$Finished = $false
while (-not $Finished)
{
while ($Me.Streams.Information.Count -eq $WorkCounter)
{
#Wait for data to be added to the information stream
Sleep -MilliSeconds 10
}
$InputData = $Me.Streams.Information[-1].MessageData
if ($InputData -eq "FINISHED")
{
$Finished = $true
}
else
{
<# Do some stuff with the $InputData #>
$ResultData += $ProgressedInputData
}
$WorkCounter++
}
Write-Information $ResultData
}
$PS = [PowerShell]::Create()
$PS.AddScript($Code) | Out-Null
$PS.AddArgument($PS) | Out-Null #Hand the PS to itself to make the streams accessible inside the thread
$Handle = $PS.BeginInvoke() | Out-Null
for ($i = 0; $i -lt 10; $i++)
{
$PS.Streams.Information.Add([System.Management.Automation.InformationRecord]::new($i, ""))
#I just gave my background thread some stuff to do without the need to instantiate a new one again
#Now this thread can do some work too...
}
$PS.Streams.Information.Add([System.Management.Automation.InformationRecord]::new("FINISHED", ""))
$Handle.AsyncWaitHandle.WaitOne() #Wait for my background thread to finish all its work
$SomeReturnValue = $PS.Streams.Information[-1].MessageData
我的实际问题是:是否有可能访问当前的 PowerShell 实例而无需像我对 $PS.AddArgument($PS)?
您不需要滥用 PowerShell.Streams
进行 two-way 通信 - PowerShell 已经有一个在两个运行空间之间桥接会话状态的工具,一个 so-called 会话状态代理!
让我们从稍微重写您的 $code
块开始:
$code = {
while($config['Enabled']){
$inputData = $null
if($queue.TryDequeue([ref]$inputData)) {
#process input data
Write-Information $inputData
}
else {
Start-Sleep -Milliseconds 50
}
}
}
请注意,我使用了两个变量,$config
和 $queue
,尽管我实际上没有参数化或以其他方式定义它们,但我们仍在使用信息流进行通信输出(你也可以使用标准输出)。
现在我们只需要创建两个可以绑定到这些变量的对象。对于 thread-safety,我们将使用:
- A
ConcurrentQueue[psobject]
输入数据 - A thread-synchronized
[hashtable]
配置数据
# Create PowerShell instance like before
$PS = [powershell]::Create()
# Create the thread-safe collections we'll be using to communicate
$queue = [System.Collections.Concurrent.ConcurrentQueue[psobject]]::new()
$config = [hashtable]::Synchronized(@{
Enabled = $true
})
# Now make those variable references available in the runspace where the backgroun code will be running
$ps.Runspace.SessionStateProxy.PSVariable.Set('queue', $queue)
$ps.Runspace.SessionStateProxy.PSVariable.Set('config', $config)
有了用于交换输入和配置数据的工具,您现在可以调用后台作业并根据通过 $queue
提供的输入观察它的行为(我强烈建议将以下语句输入到交互式提示,稍微玩一下):
# Invoke background code
$asyncHandle = $PS.BeginInvoke()
# Try adding some data to the queue
$queue.TryAdd([pscustomobject]@{ Property = "Value 123"})
# Wait for a bit
Start-Sleep -Milliseconds 100
# Observe that the queue has been emptied by the background code
Write-Host "Queue is empty: $($queue.IsEmpty)"
# Observe that the background code actually processed (and output) the data
$ps.Streams.Information
让我提供 ThreadJob
module's Start-ThreadJob
cmdlet, which ships with PowerShell (Core) v6+ 的替代方案,在 Windows PowerShell 中可以按需安装(例如,Install-Module ThreadJob -Scope CurrentUser
)
顾名思义,它提供基于 线程 的后台操作,作为 child-process 的更快 lighter-weight 替代方案 由 Start-Job
(see
因此,它 更友好,higher-level 替代通过 PowerShell SDK 管理多线程(运行空间) ,允许您使用常用的 job-management cmdlet 与后台线程交互。
一个简单的例子:
# Create a synchronized (thread-safe) queue.
$threadSafeQueue = [System.Collections.Concurrent.ConcurrentQueue[string]]::new()
# Start a thread job that keeps processing elements in the queue
# indefinitely, sleeping a little between checks for new elements.
# A special element value is used to signal that processing should end.
$jb = Start-ThreadJob {
$q = $using:threadSafeQueue # get a reference to the thread-safe queue.
$element = $null # variable to receive queue elements
while ($true) {
# Process all elements currently in the queue.
while ($q.TryDequeue([ref] $element)) {
# Check for the signal to quit, by convention a single NUL char. here.
if ("`0" -eq $element) { 'Quitting...'; return }
# Process the element at hand.
# In this example, echo the dequeued element enclosed in "[...]"
'[{0}]' -f $element
}
# Queue is (now) empty, sleep a little before checking for new elements.
Start-Sleep -MilliSeconds 100
}
}
# Populate the queue with the numbers from 1 to 10.
1..10 | ForEach-Object {
$threadSafeQueue.Enqueue($_) # This triggers activity in the background thread.
# Retrieve available output from the thread job.
$jb | Receive-Job
}
# Send the quit signal, retrieve remaining output and delete the job.
$threadSafeQueue.Enqueue("`0")
$jb | Receive-Job -Wait -AutoRemoveJob
输出:
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
[10]
Quitting...
另请参阅:
- PowerShell (Core) v7+
Foreach-Object -Parallel
功能,它同样使用线程并行处理 管道输入 。