如何将变量的当前值传递至PowerShell Runspace?
解决PowerShell WinForms串行下载的Runspace变量同步问题
核心问题分析
你遇到的问题本质是:初始化Runspace时传递的$FileList是值拷贝(空数组),按钮点击后主线程更新的$FileList无法同步到Runspace线程中。同时普通的ArrayList不是线程安全集合,多线程操作会引发异常。
解决方案:使用线程安全队列+引用传递
我们可以通过线程安全的并发队列存储下载任务,利用引用类型的特性让主线程和Runspace共享同一个队列实例,同时优化事件逻辑实现串行下载。
修改后的完整代码
Add-Type -assembly System.Windows.Forms # 使用线程安全的并发队列存储下载任务(引用类型,多线程共享实例) $FileList = New-Object System.Collections.Concurrent.ConcurrentQueue[PSObject] $webMain = New-Object System.Net.WebClient $MainForm = New-Object System.Windows.Forms.Form $MainForm.Width = 420 $MainForm.Height = 200 $MainForm.FormBorderStyle = "Fixed3d" $MainForm.MaximizeBox = $false $MainForm.StartPosition = "CenterScreen" $Button1 = New-Object System.Windows.Forms.Button $Button1.Location = New-Object System.Drawing.Size(120,50) $Button1.Size = New-Object System.Drawing.Size(160,50) $Button1.Text = "Download selected" $Button1.Name = "Button1" $Button1.Add_Click({ $Button1.Enabled = $false # 清空队列,防止重复点击累积任务 $null = $FileList.Clear() if ($CheckBox1.Checked) { $FileList.Enqueue(@{Link = "https://dl.google.com/chrome/install/standalonesetup64.exe"; Path = "D:\chrome.exe"}) } if ($CheckBox2.Checked) { $FileList.Enqueue(@{Link = "https://download.mozilla.org/?product=firefox-latest-ssl&os=win64&lang=ru"; Path = "D:\firefox.exe"}) } # 如果WebClient未忙碌且队列有任务,启动第一个下载 if (-not $webMain.IsBusy -and $FileList.Count -gt 0) { $item = $null if ($FileList.TryDequeue([ref]$item)) { $webMain.DownloadFileAsync($item.Link, $item.Path) } } }) $MainForm.Controls.Add($Button1) $CheckBox1 = New-Object System.Windows.Forms.CheckBox $CheckBox1.Location = New-Object System.Drawing.Size(10,10) $CheckBox1.Size = New-Object System.Drawing.Size(100,20) $CheckBox1.Text = "Chrome" $MainForm.Controls.Add($CheckBox1) $CheckBox2 = New-Object System.Windows.Forms.CheckBox $CheckBox2.Location = New-Object System.Drawing.Size(10,30) $CheckBox2.Size = New-Object System.Drawing.Size(100,20) $CheckBox2.Text = "Firefox" $MainForm.Controls.Add($CheckBox2) $ProgressBar1 = New-Object System.Windows.Forms.ProgressBar $ProgressBar1.Location = New-Object System.Drawing.Size(5,120) $ProgressBar1.Size = New-Object System.Drawing.Size(400,40) $ProgressBar1.Name = "ProgressBar1" $MainForm.Controls.Add($ProgressBar1) $rs = [runspacefactory]::CreateRunspace($Host) $rs.Open() # 传递队列的引用,主线程和Runspace共享同一个实例 $rs.SessionStateProxy.PSVariable.Set('form', $MainForm) $rs.SessionStateProxy.PSVariable.Set('FileList', $FileList) $ps = [powershell]::Create().AddScript({ Register-ObjectEvent -InputObject $args[0] -EventName 'DownloadProgressChanged' -SourceIdentifier 'WebMainDownloadProgressChanged' -Action { [System.Threading.Monitor]::Enter($form) $progress = $form.Controls.Find('ProgressBar1', $false)[0] $progress.Value = $eventArgs.ProgressPercentage [System.Threading.Monitor]::Exit($form) } Register-ObjectEvent -InputObject $args[0] -EventName 'DownloadFileCompleted' -SourceIdentifier 'WebMainDownloadFileCompleted' -Action { [System.Threading.Monitor]::Enter($form) $progress = $form.Controls.Find('ProgressBar1', $false)[0] $progress.Value = 0 [System.Threading.Monitor]::Exit($form) # 取出下一个下载任务,线程安全操作 $item = $null if ($FileList.TryDequeue([ref]$item)) { # 使用$sender获取WebClient实例,避免Runspace变量引用问题 $sender.DownloadFileAsync($item.Link, $item.Path) } else { # 任务全部完成,启用按钮 [System.Threading.Monitor]::Enter($form) $form.Controls.Find('Button1', $false)[0].Enabled = $true [System.Threading.Monitor]::Exit($form) } } }).AddArgument($webMain) $ps.Runspace = $rs $task = $ps.BeginInvoke() $MainForm.ShowDialog() # 清理事件和资源,避免内存泄漏 Unregister-Event -SourceIdentifier WebMainDownloadProgressChanged -ErrorAction SilentlyContinue Unregister-Event -SourceIdentifier WebMainDownloadFileCompleted -ErrorAction SilentlyContinue $webMain.Dispose() $MainForm.Dispose()
关键修改说明
- 线程安全队列:用
ConcurrentQueue替代普通数组/ArrayList,所有队列操作(入队、出队)都是线程安全的,无需手动加锁 - 引用传递:Runspace中设置的
$FileList是队列实例的引用,主线程按钮点击时修改队列,Runspace线程能实时获取最新内容 - 事件逻辑优化:
- 按钮点击时清空队列,避免重复点击累积任务
- 下载完成事件中自动取出下一个任务启动下载,队列为空时启用按钮
- 用
$sender获取WebClient实例,解决Runspace中无法直接访问主线程$webMain的问题
- 资源清理:脚本结束时注销事件、释放WebClient和窗体资源,避免内存泄漏
内容的提问来源于stack exchange,提问作者Dron
相关产品推荐
相关产品推荐

