PowerShell: Runspace problem with DownloadFileAsync PowerShell: Runspace problem with DownloadFileAsync multithreading multithreading

PowerShell: Runspace problem with DownloadFileAsync


Thanks stej for the nod.

Andrey, powershell has its own threadpool and each service thread keeps a threadstatic pointer to a runspace (the System.Management.Automation.Runspaces.Runspace.DefaultRunspace static member exposes this - and would be a null ref in your callbacks.) Ultimately this means it's difficult - especially in script - to use your own threadpool (as is provided by .NET for async methods) to execute scriptblocks.

PowerShell 2.0

Regardless, there is no need to play with this as powershell v2 has full support for eventing:

$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

If you're stuck on v1 (this is not specifically for you as you mention v2 in the question) you can use my powershell 1.0 eventing snap-in at http://pseventing.codeplex.com/

Async Callbacks

Another tricky area in .NET is async callbacks. There is nothing directly in v1 or v2 of powershell that can help you here, but you can convert an async callback to an event with some simple plumbing and then deal with that event using regular eventing. I posted a script for this (New-ScriptBlockCallback) at http://poshcode.org/1382

Hope this helps,

-Oisin


I see that you use async call so that you can show the progress. Then you can use BitsTransfer module for that. It shows the progress by default:

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

If you would like to transfer the file in the background, you could use something like this:

Import-Module BitsTransfer$timer = New-Object Timers.Timer$timer.Interval = 300Register-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 thatUnregister-Event -SourceIdentifier mytransfer$timer.Dispose()

The key parameter is -async. It starts the transfer in background. I haven't found any event triggered by the transfer, so I query the job each second to report the state via Timers.Timer object.

However with this solution, it is needed to unregister the event and dispose the timer. Some time ago I had problems with unregistering in the scriptblock passed as -Action (it could be in the if branch), so I unregister the event in separate command.


I think @oising (x0n) has some solution on his blog. He'll tell you that hopefully and that would be answer for your question.