我正在创建一个带有GUI的powershell脚本,该脚本将用户配置文件从选定的源磁盘复制到目标磁盘。我用XAML创建了GUI,使用VS Community 2019。脚本的工作方式如下:选择源磁盘、目标磁盘、用户配置文件和要复制的文件夹。当您按下按钮“Start”时,它调用一个名为Backup_data的函数,在此创建一个运行空间。在这个运行空间中,只有一个小小的**Copy-Item,**以您选择的内容作为参数。
脚本工作正常,所有想要的项目都被正确复制。问题是GUI在复制过程中被冻结了(没有“不响应”的消息或其他什么,它只是完全冻结了;不能点击任何地方,不能移动窗口)。我已经看到使用运行空间可以解决这个问题,但对我来说不是。我错过了什么吗?
下面是函数Backup_Data
:
Function BackupData {
##CREATE RUNSPACE
$PowerShell = [powershell]::Create()
[void]$PowerShell.AddScript( {
Param ($global:ReturnedDiskSource, $global:SelectedUser, $global:SelectedFolders, $global:ReturnedDiskDestination)
##SCRIPT BLOCK
foreach ($item in $global:SelectedFolders) {
Copy-Item -Path "$global:ReturnedDiskSource\Users\$global:SelectedUser\$item" -Destination "$global:ReturnedDiskDestination\Users\$global:SelectedUser\$item" -Force -Recurse
}
}).AddArgument($global:ReturnedDiskSource).AddArgument($global:SelectedUser).AddArgument($global:SelectedFolders).AddArgument($global:ReturnedDiskDestination)
#Invoke the command
$PowerShell.Invoke()
$PowerShell.Dispose()
}
2条答案
按热度按时间jhdbpxl91#
PowerShell SDK的**
PowerShell.Invoke()
方法是 * 同步的***,因此通过设计,当其他运行空间(线程)中的脚本运行时,它会 * 阻塞 *。必须使用异步
PowerShell.BeginInvoke()
方法。请注意,使用PowerShell SDK有一个更简单的替代方法:
ThreadJob
模块的**Start-ThreadJob
**cmdlet,一个基于 * thread * 的替代方案,用于替代以Start-Job
启动的基于 * child-process * 的常规后台作业,它与所有其他*-Job
cmdlet兼容。Start-ThreadJob
附带 * PowerShell [Core] 7 +*,可从 * Windows PowerShell * 中的PowerShell Gallery(Install-Module ThreadJob
)安装。如果代码需要从附加到WPF窗口中的控件的事件处理程序运行,则需要更多的工作,因为**
Start-Sleep
不能 * 使用,因为它 * 阻止GUI事件的处理 * 并因此冻结窗口。[System.Windows.Forms.Application]::DoEvents()
),WPF * 没有 * 等效的方法,但可以 * 手动 * 添加,如DispatcherFrame
documentation所示。下面的例子:
Start-ThreadJob
启动后台操作:Start-Job
也可以工作,但这将在一个 * 子进程 * 而不是线程中运行代码,这要慢得多,并有其他重要的后果。[powershell]
)的使用也并不难,但是线程作业更符合PowerShell习惯,并且更容易通过常规的*-Job
cmdlet进行管理。DoEvents()
类似的函数DoWpfEvents
,它是从DispatcherFrame
documentation改编而来的,在GUI事件处理的每个循环操作中都被调用。[System.Windows.Forms.Application]::DoEvents()
-事实上,如果您不介意同时加载 * WPF和WinForms程序集,您可以 * 直接 * 使用后者。.ShowModal()
方式调用窗口一样,前台线程和控制台会话在窗口显示时会被阻塞。避免这种情况的最简单方法是改为在隐藏的子进程中运行代码;假设代码在脚本wpfDemo.ps1
中:你也可以通过SDK来实现,这样会更快,但它更冗长和麻烦:
$runspace = [runspacefactory]::CreateRunspace() $runspace.ApartmentState = 'STA'; $runspace.Open(); $ps = [powershell]::Create(); $ps.Runspace = $runspace; $null = $ps.AddScript((Get-Content -Raw wpfDemo.ps1)).BeginInvoke()
此示例屏幕截图显示了一个已完成的后台操作和一个正在进行的操作(支持并行运行它们);注意启动正在进行的操作的按钮如何在操作期间被禁用,以防止重新输入:
hsvhsicv2#
我一整天都在寻找一个解决方案,我终于找到了一个,所以我打算把它贴在那里,给那些有同样问题的人。
首先,查看这篇文章:https://smsagent.blog/2015/09/07/powershell-tip-utilizing-runspaces-for-responsive-wpf-gui-applications/它解释得很好,并向您展示了如何在WPF GUI中正确使用运行空间。你只需要将$Window变量替换为$Synchhash。Window:
在代码中插入运行空间函数:
并在所需的事件处理程序中使用您指定的参数调用它:
不要忘记结束你的运行空间: