【问题标题】:PowerShell: Runspace problem with DownloadFileAsyncPowerShell:DownloadFileAsync 的运行空间问题
【发布时间】:2011-02-07 20:16:13
【问题描述】:

我需要在 PowerShell 2.0 中使用 WebClient 下载文件,并且我想显示下载进度,所以我这样做了:

$activity = "Downloading"

$client = New-Object System.Net.WebClient
$urlAsUri = New-Object System.Uri($url)

$event = New-Object System.Threading.ManualResetEvent($false)

$downloadProgress = [System.Net.DownloadProgressChangedEventHandler] {
    $progress = [int]((100.0 * $_.BytesReceived) / $_.TotalBytesToReceive)
    Write-Progress -Activity $activity -Status "${progress}% done" -PercentComplete $progress
}

$downloadComplete = [System.ComponentModel.AsyncCompletedEventHandler] {
    Write-Progress -Activity $activity -Completed
    $event.Set()
}

$client.add_DownloadFileCompleted($downloadComplete) 
$client.add_DownloadProgressChanged($downloadProgress)

Write-Progress -Activity $activity -Status "0% done" -PercentComplete 0
$client.DownloadFileAsync($urlAsUri, $file)    

$event.WaitOne()

There is no Runspace available to run scripts in this thread. 处理程序中的代码出现错误 There is no Runspace available to run scripts in this thread.,这是合乎逻辑的。但是,如何为(可能)属于ThreadPool 的线程提供Runspace

更新: 请注意,这个问题的两个答案都值得一读,如果可以的话,我会同时接受。

【问题讨论】:

    标签: multithreading powershell


    【解决方案1】:

    感谢 stej 的点头。

    Andrey,powershell 有自己的线程池,每个服务线程都保留一个指向运行空间的 threadstatic 指针(System.Management.Automation.Runspaces.Runspace.DefaultRunspace 静态成员公开了这一点 - 并且在您的回调中将是一个空引用。)最终这意味着很难 - 特别是在脚本中 - 使用您自己的线程池(由 .NET 为异步方法提供)来执行脚本块。

    PowerShell 2.0

    无论如何,没有必要玩这个,因为 powershell v2 完全支持事件:

    $client = New-Object System.Net.WebClient
    $url = [uri]"http://download.microsoft.com/download/6/2/F/" +
        "62F70029-A592-4158-BB51-E102812CBD4F/IE9-Windows7-x64-enu.exe"
    
    try {
    
       Register-ObjectEvent $client DownloadProgressChanged -action {     
    
            Write-Progress -Activity "Downloading" -Status `
                ("{0} of {1}" -f $eventargs.BytesReceived, $eventargs.TotalBytesToReceive) `
                -PercentComplete $eventargs.ProgressPercentage    
        }
    
        Register-ObjectEvent $client DownloadFileCompleted -SourceIdentifier Finished
    
        $file = "c:\temp\ie9-beta.exe"
        $client.DownloadFileAsync($url, $file)
    
        # optionally wait, but you can break out and it will still write progress
        Wait-Event -SourceIdentifier Finished
    
    } finally { 
        $client.dispose()
    }
    

    PowerShell v1.0

    如果您被困在 v1 上(这不是专门为您准备的,因为您在问题中提到了 v2),您可以使用我的 powershell 1.0 事件管理单元http://pseventing.codeplex.com/

    异步回调

    .NET 中另一个棘手的领域是异步回调。 powershell v1 或 v2 中没有任何东西可以直接帮助您,但是您可以通过一些简单的管道将异步回调转换为事件,然后使用常规事件处理该事件。我在http://poshcode.org/1382发布了一个脚本(New-ScriptBlockCallback)@

    希望对你有帮助,

    -奥辛

    【讨论】:

    【解决方案2】:

    我看到您使用异步调用以便显示进度。然后你可以使用 BitsTransfer 模块。默认显示进度:

    Import-Module BitsTransfer
    Start-BitsTransfer -Source $url -dest d:\temp\yourfile.zip
    

    如果您想在后台传输文件,您可以使用如下方式:

    Import-Module BitsTransfer
    
    $timer = New-Object Timers.Timer
    $timer.Interval = 300
    Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action {
        if ($transfer.JobState -ne 'Transferring') { 
            $timer.Enabled = 0; 
            Write-Progress -Completed -Activity Downloading -Status done
            return 
        }
        $progress = [int](100* $transfer.BytesTransferred/$transfer.BytesTotal)
        Write-Progress -Activity Downloading -Status "$progress% done" -PercentComplete $progress
    } -sourceId mytransfer
    $transfer = Start-BitsTransfer -Source $url -dest d:\temp\yourfile.zip -async
    $timer.Enabled = 1
    
    # after that
    Unregister-Event -SourceIdentifier mytransfer
    $timer.Dispose()
    

    关键参数是-async。它在后台启动传输。我没有发现任何由传输触发的事件,所以我每秒查询一次作业以通过Timers.Timer 对象报告状态。

    但是,使用此解决方案,需要取消注册事件并处置计时器。前段时间我在以-Action 传递的脚本块中注销时遇到了问题(它可能在if 分支中),所以我在单独的命令中注销了该事件。


    我认为@oising (x0n) 在他的博客上有一些解决方案。他会满怀希望地告诉你,这就是你问题的答案。

    【讨论】:

    • 谢谢,这真是天才。比我的解决方案好得多。现在我会稍等片刻,看看是否有人在接受之前回答了运行空间部分(只是好奇这是怎么回事)。
    • 我会选择另一种方式来获得灵感 :)
    • @stej - 您可以使用“unregister-event -subscriberid $eventsubscription.subscriberid”从处理程序中取消注册
    • 我试图避免从处理程序调用Unregister-event,因为前段时间connect.microsoft.com/PowerShell/feedback/details/541360/…。但是今天,我无法重现它。
    • 这很酷。我对接受哪个答案感到困惑,但另一个与我的问题的标题/文本更直接相关,并且可能对 Google 员工更有用。虽然如果可以的话,我会接受两者。
    猜你喜欢
    • 1970-01-01
    • 2015-09-12
    • 1970-01-01
    • 1970-01-01
    • 2013-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-23
    相关资源
    最近更新 更多