乔布斯真的在 powershell 的后台工作吗?
Do Jobs really work in background in powershell?
我正在尝试实现在用户单击按钮后启动的后台命令,到目前为止,我总是以 UI 在作业进行时锁定而告终。我目前正在使用一个有点耗时的 for 循环来检查我的 UI 是否锁定。当 UI 被释放时,我该怎么做才能让作业在后台工作。我不确定是否要通过添加运行空间来使代码复杂化。我做错了什么?
$inputXML = @"
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication2"
mc:Ignorable="d"
Title="Create User" Height="448.05" Width="656.017" ResizeMode="NoResize">
<Grid Margin="0,0,-6.8,-0.8" Background="#FFD7D7D7">
<Grid.RowDefinitions>
<RowDefinition Height="403*"/>
<RowDefinition Height="18*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button x:Name="Create_User" Content="Create User" HorizontalAlignment="Left" Margin="67,224,0,0" VerticalAlignment="Top" Width="300" Height="26" RenderTransformOrigin="0.551,-0.671" IsEnabled="True">
<Button.Background>
<LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFF3F3F3" Offset="0"/>
<GradientStop Color="#FFEBEBEB" Offset="0.5"/>
<GradientStop Color="#FFDDDDDD" Offset="0.5"/>
<GradientStop Color="#FFCDCDCD" Offset="1"/>
</LinearGradientBrush>
</Button.Background>
</Button>
<Label x:Name="fname" Content="" HorizontalAlignment="Left" Margin="43,11,0,0" VerticalAlignment="Top" Height="26" Width="10"/>
<Label x:Name="fname1" Content="First Name" HorizontalAlignment="Left" Margin="88,54,0,0" VerticalAlignment="Top" Width="72" RenderTransformOrigin="0.513,1.469" Height="26"/>
<Label x:Name="lname" Content="Last Name" HorizontalAlignment="Left" Margin="88,83,0,0" VerticalAlignment="Top" RenderTransformOrigin="-0.167,-0.458" Width="72" Height="25"/>
<TextBox x:Name="fnametxt" Height="23" Margin="167,54,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="0.501,0.452" HorizontalAlignment="Left" Width="136"/>
<Button x:Name="exitbtn" Content="Exit" HorizontalAlignment="Left" VerticalAlignment="Top" Width="135" Margin="447,365,0,0" Height="38" RenderTransformOrigin="0.489,0.462"/>
<Label x:Name="label" Content="Password" HorizontalAlignment="Left" Margin="92,113,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.064,0.601" Height="26" Width="68"/>
<PasswordBox x:Name="passwordBox" Margin="167,113,0,0" VerticalAlignment="Top" Height="24" HorizontalAlignment="Left" Width="136"/>
<TextBox x:Name="stsbox" HorizontalAlignment="Left" Height="62" Margin="67,267,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="300" Background="#FFDADADA" Foreground="Black" Opacity="0.45" SelectionBrush="#FF1D6EBF" RenderTransformOrigin="0.503,-0.59" IsReadOnly="True"/>
<TextBox x:Name="lnametxt" Height="23" Margin="167,85,0,0" TextWrapping="Wrap" VerticalAlignment="Top" HorizontalAlignment="Left" Width="136"/>
</Grid>
</Window>
"@
$inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window'
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[xml]$xaml = $inputXML
#Read XAML
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
try{$Form=[Windows.Markup.XamlReader]::Load( $reader )}
catch{Write-Host "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed."}
$xaml.SelectNodes("//*[@Name]") | %{Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name) -Scope Global}
Function global:Get-FormVariables{
if ($global:ReadmeDisplay -ne $true) {$global:ReadmeDisplay=$true}
#write-host "Found the following interactable elements from our form" -ForegroundColor Cyan
get-variable WPF*
}
Get-FormVariables
$WPFCreate_User.Add_Click({
$test = { for($i=1; $i -le 100000; $i++){
$z = $i + $z
$z
}}
$job = Start-Job $test
Wait-Job $job
Receive-Job $job -OutVariable results
Remove-Job $job
$WPFstsbox.text = "$results`n"
})
$WPFexitbtn.add_click({
$form.Close() | out-null
exit})
$form.ShowDialog() | Out-null
是的,PSJob 是真正的“后台”作业。
当您调用 Start-Job
时,会启动一个 单独的进程 ,执行您的 job/command.
在您的脚本中,作业本身不会阻塞调用线程,但您的后续命令 (Wait-Job $job
) 会。
如果您只是从 Click
处理程序中触发 Start-Job
和 return,您的 UI 将不会锁定:
$button.add_Click({
$jobCode = { Start-Sleep -Seconds 10 }
$job = Start-Job $jobCode
})
您会看到 UI 在不到 10 秒的时间内再次变得有响应。
问题是您无法再访问 $job
并且不再控制它何时显示。
为了弥补这一点,您需要后台线程或定时事件来为您定期检查作业并输出结果。
您可以为此使用 Timer
和 PowerShell 的内置事件基础结构:
# Create timer
$backgroundTimer = New-Object System.Timers.Timer
# Set interval (ms)
$backgroundTimer.Interval = 500
# Make sure timer stops raising events after each interval
$backgroundTimer.AutoReset = $false
# Have powershell "listen" for the event in the background
Register-ObjectEvent $backgroundTimer -EventName 'Elapsed' -SourceIdentifier 'timerElapsed' -Action {
# Loop through any completed jobs with data to return, from "oldest" to "newest"
while(($finishedJob = Get-Job |Where-Object {$_.State -eq 'Completed' -and $_.HasMoreData}|Select-Object -First 1))
{
# Update the text box on your WPF form with the results
# NOTE: the event is executed in a separate scope, thus the "global:" scope prefix
$global:WPFstsbox.Text = "$(Receive-Job $finishedJob)`n"
# Clean up the job
Remove-Job $finishedJob
}
# Restart timer
$backgroundTimer.Start()
}
# Enable the timer
$backgroundTimer.Enabled = $true
我正在尝试实现在用户单击按钮后启动的后台命令,到目前为止,我总是以 UI 在作业进行时锁定而告终。我目前正在使用一个有点耗时的 for 循环来检查我的 UI 是否锁定。当 UI 被释放时,我该怎么做才能让作业在后台工作。我不确定是否要通过添加运行空间来使代码复杂化。我做错了什么?
$inputXML = @"
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication2"
mc:Ignorable="d"
Title="Create User" Height="448.05" Width="656.017" ResizeMode="NoResize">
<Grid Margin="0,0,-6.8,-0.8" Background="#FFD7D7D7">
<Grid.RowDefinitions>
<RowDefinition Height="403*"/>
<RowDefinition Height="18*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button x:Name="Create_User" Content="Create User" HorizontalAlignment="Left" Margin="67,224,0,0" VerticalAlignment="Top" Width="300" Height="26" RenderTransformOrigin="0.551,-0.671" IsEnabled="True">
<Button.Background>
<LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFF3F3F3" Offset="0"/>
<GradientStop Color="#FFEBEBEB" Offset="0.5"/>
<GradientStop Color="#FFDDDDDD" Offset="0.5"/>
<GradientStop Color="#FFCDCDCD" Offset="1"/>
</LinearGradientBrush>
</Button.Background>
</Button>
<Label x:Name="fname" Content="" HorizontalAlignment="Left" Margin="43,11,0,0" VerticalAlignment="Top" Height="26" Width="10"/>
<Label x:Name="fname1" Content="First Name" HorizontalAlignment="Left" Margin="88,54,0,0" VerticalAlignment="Top" Width="72" RenderTransformOrigin="0.513,1.469" Height="26"/>
<Label x:Name="lname" Content="Last Name" HorizontalAlignment="Left" Margin="88,83,0,0" VerticalAlignment="Top" RenderTransformOrigin="-0.167,-0.458" Width="72" Height="25"/>
<TextBox x:Name="fnametxt" Height="23" Margin="167,54,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="0.501,0.452" HorizontalAlignment="Left" Width="136"/>
<Button x:Name="exitbtn" Content="Exit" HorizontalAlignment="Left" VerticalAlignment="Top" Width="135" Margin="447,365,0,0" Height="38" RenderTransformOrigin="0.489,0.462"/>
<Label x:Name="label" Content="Password" HorizontalAlignment="Left" Margin="92,113,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.064,0.601" Height="26" Width="68"/>
<PasswordBox x:Name="passwordBox" Margin="167,113,0,0" VerticalAlignment="Top" Height="24" HorizontalAlignment="Left" Width="136"/>
<TextBox x:Name="stsbox" HorizontalAlignment="Left" Height="62" Margin="67,267,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="300" Background="#FFDADADA" Foreground="Black" Opacity="0.45" SelectionBrush="#FF1D6EBF" RenderTransformOrigin="0.503,-0.59" IsReadOnly="True"/>
<TextBox x:Name="lnametxt" Height="23" Margin="167,85,0,0" TextWrapping="Wrap" VerticalAlignment="Top" HorizontalAlignment="Left" Width="136"/>
</Grid>
</Window>
"@
$inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window'
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[xml]$xaml = $inputXML
#Read XAML
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
try{$Form=[Windows.Markup.XamlReader]::Load( $reader )}
catch{Write-Host "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed."}
$xaml.SelectNodes("//*[@Name]") | %{Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name) -Scope Global}
Function global:Get-FormVariables{
if ($global:ReadmeDisplay -ne $true) {$global:ReadmeDisplay=$true}
#write-host "Found the following interactable elements from our form" -ForegroundColor Cyan
get-variable WPF*
}
Get-FormVariables
$WPFCreate_User.Add_Click({
$test = { for($i=1; $i -le 100000; $i++){
$z = $i + $z
$z
}}
$job = Start-Job $test
Wait-Job $job
Receive-Job $job -OutVariable results
Remove-Job $job
$WPFstsbox.text = "$results`n"
})
$WPFexitbtn.add_click({
$form.Close() | out-null
exit})
$form.ShowDialog() | Out-null
是的,PSJob 是真正的“后台”作业。
当您调用 Start-Job
时,会启动一个 单独的进程 ,执行您的 job/command.
在您的脚本中,作业本身不会阻塞调用线程,但您的后续命令 (Wait-Job $job
) 会。
如果您只是从 Click
处理程序中触发 Start-Job
和 return,您的 UI 将不会锁定:
$button.add_Click({
$jobCode = { Start-Sleep -Seconds 10 }
$job = Start-Job $jobCode
})
您会看到 UI 在不到 10 秒的时间内再次变得有响应。
问题是您无法再访问 $job
并且不再控制它何时显示。
为了弥补这一点,您需要后台线程或定时事件来为您定期检查作业并输出结果。
您可以为此使用 Timer
和 PowerShell 的内置事件基础结构:
# Create timer
$backgroundTimer = New-Object System.Timers.Timer
# Set interval (ms)
$backgroundTimer.Interval = 500
# Make sure timer stops raising events after each interval
$backgroundTimer.AutoReset = $false
# Have powershell "listen" for the event in the background
Register-ObjectEvent $backgroundTimer -EventName 'Elapsed' -SourceIdentifier 'timerElapsed' -Action {
# Loop through any completed jobs with data to return, from "oldest" to "newest"
while(($finishedJob = Get-Job |Where-Object {$_.State -eq 'Completed' -and $_.HasMoreData}|Select-Object -First 1))
{
# Update the text box on your WPF form with the results
# NOTE: the event is executed in a separate scope, thus the "global:" scope prefix
$global:WPFstsbox.Text = "$(Receive-Job $finishedJob)`n"
# Clean up the job
Remove-Job $finishedJob
}
# Restart timer
$backgroundTimer.Start()
}
# Enable the timer
$backgroundTimer.Enabled = $true