PowerShell: проблема с пространством выполнения при использовании DownloadFileAsync - PullRequest
11 голосов
/ 07 февраля 2011

Мне нужно было загрузить файл, используя WebClient в PowerShell 2.0, и я хотел показать прогресс загрузки, поэтому я сделал это следующим образом:

$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. для кода в обработчике $downloadProgress, что логично. Тем не менее, как я могу предоставить Runspace для потока, который (вероятно) принадлежит к ThreadPool?

UPDATE: Обратите внимание, что оба ответа на этот вопрос заслуживают прочтения, и я бы принял оба, если бы мог.

Ответы [ 2 ]

13 голосов
/ 08 февраля 2011

Спасибо, Стей, за поклон.

Андрей, powershell имеет свой собственный пул потоков, и каждый поток службы хранит потоковый указатель на пространство выполнения (статический член 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 - асинхронные обратные вызовы. Здесь нет ничего такого, что могло бы помочь вам в v1 или v2 в PowerShell, но вы можете преобразовать асинхронный обратный вызов в событие с некоторой простой сантехникой, а затем обработать это событие, используя обычные события. Я разместил скрипт для этого (New-ScriptBlockCallback) на http://poshcode.org/1382

Надеюсь, это поможет,

-Oisin

6 голосов
/ 07 февраля 2011

Я вижу, что вы используете асинхронный вызов, чтобы вы могли показать прогресс.Тогда вы можете использовать модуль 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), поэтому я отменяю регистрацию события в отдельной команде.(x0n) имеет какое-то решение в своем блоге.Он скажет вам это с надеждой, и это будет ответом на ваш вопрос.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...