2011-02-07 20 views
11

necesitaba para descargar el archivo usando WebClient en PowerShell 2.0, y yo quería mostrar progreso de la descarga, por lo que hizo de esta manera:PowerShell: problema de espacio de ejecución con DownloadFileAsync

$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() 

estoy recibiendo un error de There is no Runspace available to run scripts in this thread. para el código en el controlador $downloadProgress, que es lógico. Sin embargo, ¿cómo proporciono un Runspace para el hilo que (probablemente) pertenece al ThreadPool?

ACTUALIZACIÓN: Tenga en cuenta que ambos respuestas a esta pregunta vale la pena leer, y me aceptarían tanto si pudiera.

Respuesta

12

Gracias stej por el guiño.

Andrey, powershell tiene su propio threadpool y cada cadena de servicio mantiene un puntero threadstatic a un espacio de ejecución (el miembro estático System.Management.Automation.Runspaces.Runspace.DefaultRunspace expone esto, y sería una referencia nula en sus callbacks.) En última instancia, esto significa que es difícil, especialmente en script, utilizar su propio threadpool (como lo proporciona .NET para métodos async) para ejecutar scriptblocks.

PowerShell 2.0

pesar de todo, no hay necesidad de jugar con esto como PowerShell v2 tiene soporte completo para el concurso completo:

$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

Si está atascado en v1 (esto no es específicamente para ti, ya que mencionas v2 en la pregunta) puedes usar mi complemento de eventing de powershell 1.0 en http://pseventing.codeplex.com/

asíncrono devoluciones de llamada

Otra área difícil de NET es devoluciones de llamada asincrónicas.No hay nada directamente en v1 o v2 de powershell que pueda ayudarlo aquí, pero puede convertir una devolución de llamada asincrónica a un evento con una plomería simple y luego tratar ese evento usando eventos regulares. He publicado un script para esto (Nueva-ScriptBlockCallback) en http://poshcode.org/1382

Espero que esto ayude,

-Oisin

+0

Sí, el 'New-ScriptBlockCallback' era exactamente lo que tenía en mente cuando mencioné que tendría una respuesta :) – stej

+0

genial, ahora volví a la pregunta y No tengo ni idea de qué respuesta aceptar :) –

+0

¡Hola, hay un método de 'Agregar-Tipo'! y aquí estaba tratando de emitir IL necesario para establecer Runspace. –

6

Veo que usa la llamada asincrónica para que pueda mostrar el progreso. Entonces puedes usar el módulo BitsTransfer para eso. Se muestra el progreso de forma predeterminada:

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

Si desea transferir el archivo en segundo plano, se puede usar algo como esto:

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() 

El parámetro clave es -async. Comienza la transferencia en segundo plano. No he encontrado ningún evento desencadenado por la transferencia, por lo que consulto el trabajo cada segundo para informar el estado a través del objeto Timers.Timer.

Sin embargo, con esta solución, es necesario anular el registro del evento y eliminar el temporizador. Hace algún tiempo tuve problemas para anular el registro en el scriptbloque pasado como -Action (podría estar en la rama if), por lo que anula el registro del evento en un comando por separado.


Creo que @oising (x0n) tiene alguna solución en su blog. Él te dirá eso con suerte y esa sería la respuesta a tu pregunta.

+0

Gracias, esto es puro genio. Mucho mejor que mi solución. Ahora esperaré un poco para ver si alguien responde la parte de Runspace antes de aceptar (solo curiosidad por saber cuál es el problema). –

+0

Voy a otra opción solo por inspiración :) – stej

+0

Runspace parte dirigida. – x0n

Cuestiones relacionadas