如果我在 powershell 脚本中使用 System.Windows.Forms,使用 System.Net.WebClient 异步上传文件的 Register-ObjectEvent 将停止工作

问题描述 投票:0回答:1

我想在Form上从网上下载文件,并在ProgressBar中显示下载进度。为此,我订阅加载事件并执行异步加载。一切正常。这是简化的代码(删除了所有不必要的):

Add-Type -assembly System.Windows.Forms

$isDownloaded = $False
$webMain = New-Object System.Net.WebClient

Register-ObjectEvent -InputObject $webMain -EventName 'DownloadFileCompleted' -SourceIdentifier WebMainDownloadFileCompleted -Action {    
    $Global:isDownloaded = $True
}

Register-ObjectEvent -InputObject $webMain -EventName 'DownloadProgressChanged' -SourceIdentifier WebMainDownloadProgressChanged -Action {
    $Global:Data = $event
}

function DownloadFile($Link, $Path, $Name) {

    write-host "begin"

    $Global:webMain.DownloadFileAsync($Link, $Path)
    
    While (!$isDownloaded) {
        $percent = $Global:Data.SourceArgs.ProgressPercentage
        If ($percent -ne $null) {
            write-host $percent
        }
        Wait-Event -Timeout 1
    }

    write-host "end"
}

DownloadFile 'https://www.7-zip.org/a/7z2301-x64.exe' 'D:\7Zip.exe' '7Zip'

一切正常。现在我将它添加到代码中的任何位置

$Form1 = New-Object System.Windows.Forms.Form

$Button1 = New-Object System.Windows.Forms.Button

并且脚本不起作用。 DownloadProgressChanged 和 DownloadFileCompleted 事件根本不会发生。

问题:为什么仅创建表单或按钮就会干扰脚本?

如果没有 System.Windows.Forms.Form,代码可以工作,但我最终需要创建一个表单并在其上渲染加载。

DownloadFileAsync 有效 - 文件已下载,但在使用任何 New-Object System.Windows.Forms.* 时,Register-ObjectEvent 本身中的事件不会发生(但没有它们也能正常工作)。

powershell winforms asynchronous events
1个回答
0
投票

如评论中所述,PowerShell 对于异步编程来说并不是一种很好的语言,问题在于

.ShowDialog()
会阻塞线程并且不允许您的事件正常执行。解决方案是在单独的运行空间中注册事件,下面是如何实现这一点的最小示例(尽可能最小)。我添加了一些指针注释来帮助您理解逻辑,尽管代码显然并不容易,如前所述,PowerShell 不是为此设计的语言,C# 会给您更轻松的时间。

演示:

代码:

Add-Type -Assembly System.Windows.Forms

[System.Windows.Forms.Application]::EnableVisualStyles()

$form = [System.Windows.Forms.Form]@{
    Size            = '500, 150'
    FormBorderStyle = 'Fixed3d'
}
$btn = [System.Windows.Forms.Button]@{
    Name     = 'MyButton'
    Text     = 'Click Me!'
    Size     = '90, 30'
    Location = '370, 70'
    Anchor   = 'Bottom, Right'
}
$btn.Add_Click({
    # disable the button here to allow a single download at a time
    $this.Enabled = $false
    # hardcoded link here for demo
    $downloader.DownloadFileAsync('https://www.7-zip.org/a/7z2301-x64.exe', "$pwd\7Zip.exe")
})
$progress = [System.Windows.Forms.ProgressBar]@{
    Name     = 'MyProgressBar'
    Size     = '460, 40'
    Location = '10, 10'
    Step     = 0
    Value    = 0
    Maximum  = 100
}
$form.Controls.AddRange(($btn, $progress))

# create a WebClient instance
$downloader = [System.Net.WebClient]::new()
# create a new runspace where the Download Events will execute
$rs = [runspacefactory]::CreateRunspace($Host)
$rs.Open()
# add the `$form` instance to the runspace Global scope
$rs.SessionStateProxy.PSVariable.Set(
    [psvariable]::new(
        'form',
        $form,
        [System.Management.Automation.ScopedItemOptions]::AllScope))

# the code that will initiate the events in the new runspace
$ps = [powershell]::Create().AddScript({
    $registerObjectEventSplat = @{
        InputObject      = $args[0] # $args[0] = $form
        EventName        = 'DownloadProgressChanged'
        SourceIdentifier = 'WebMainDownloadProgressChanged'
        Action           = {
            $progress = $form.Controls.Find('MyProgressBar', $false)[0]
            # for demo, in addition to increment the progress bar,
            # show the percentage to the console
            $eventArgs.ProgressPercentage | Out-Host
            # increment the progress bar
            $progress.Value = $eventArgs.ProgressPercentage
        }
    }
    Register-ObjectEvent @registerObjectEventSplat
    $registerObjectEventSplat['EventName'] = 'DownloadFileCompleted'
    $registerObjectEventSplat['SourceIdentifier'] = 'WebMainDownloadFileCompleted'
    $registerObjectEventSplat['Action'] = {
        # when the download is completed, enable the button
        $form.Controls.Find('MyButton', $false)[0].Enabled = $true
        # and show this to the console
        Write-Host 'Download Completed!'
    }
    Register-ObjectEvent @registerObjectEventSplat
}).AddArgument($downloader)
$ps.Runspace = $rs
$task = $ps.BeginInvoke()
$form.ShowDialog()
© www.soinside.com 2019 - 2024. All rights reserved.