You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将变量的当前值传递至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()

关键修改说明

  1. 线程安全队列:用ConcurrentQueue替代普通数组/ArrayList,所有队列操作(入队、出队)都是线程安全的,无需手动加锁
  2. 引用传递:Runspace中设置的$FileList是队列实例的引用,主线程按钮点击时修改队列,Runspace线程能实时获取最新内容
  3. 事件逻辑优化:
    • 按钮点击时清空队列,避免重复点击累积任务
    • 下载完成事件中自动取出下一个任务启动下载,队列为空时启用按钮
    • 用$sender获取WebClient实例,解决Runspace中无法直接访问主线程$webMain的问题
  4. 资源清理:脚本结束时注销事件、释放WebClient和窗体资源,避免内存泄漏

内容的提问来源于stack exchange,提问作者Dron

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 15:47:33