2011-02-07 26 views
11

tôi cần phải tải về tập tin sử dụng WebClient trong PowerShell 2.0, và tôi muốn hiển thị tiến trình download, vì vậy tôi đã làm nó theo cách này:PowerShell: Runspace vấn đề với 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() 

Tôi nhận được một lỗi There is no Runspace available to run scripts in this thread. cho mã trong bộ xử lý $downloadProgress, hợp lý. Tuy nhiên, làm thế nào để tôi cung cấp một số Runspace cho chuỗi (có thể) thuộc về ThreadPool?

UPDATE: Lưu ý rằng cả câu trả lời cho câu hỏi này là đáng đọc, và tôi sẽ chấp nhận cả hai nếu tôi có thể.

Trả lời

12

Cảm ơn stej vì đã gật đầu.

Andrey, powershell có threadpool riêng của nó và mỗi thread dịch vụ giữ một con trỏ threadstatic đến một runspace (System.Management.Automation.Runspaces.Runspace.DefaultRunspace thành viên tĩnh exposes này - và sẽ là một ref null trong callbacks của bạn. Cuối cùng, điều này có nghĩa là nó khó khăn - đặc biệt là trong kịch bản - sử dụng threadpool của riêng bạn (như được cung cấp bởi .NET cho các phương thức async) để thực thi các kịch bản lệnh.

PowerShell 2.0

Bất kể, không có nhu cầu để chơi với điều này như PowerShell v2 đã hỗ trợ đầy đủ cho 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

Nếu bạn đang bị mắc kẹt trên v1 (đây không phải là đặc biệt cho bạn khi bạn đề cập đến v2 trong câu hỏi), bạn có thể sử dụng snapshot eventhell 1.0 của tôi tại http://pseventing.codeplex.com/

Async Callback

Một lĩnh vực khó khăn trong .NET là callbacks async.Không có gì trực tiếp trong v1 hoặc v2 của PowerShell có thể giúp bạn ở đây, nhưng bạn có thể chuyển đổi một cuộc gọi lại async thành một sự kiện với một số hệ thống ống nước đơn giản và sau đó đối phó với sự kiện đó bằng cách sử dụng sự kiện thường xuyên. Tôi gửi một kịch bản cho việc này (New-ScriptBlockCallback) tại http://poshcode.org/1382

Hope this helps,

-Oisin

+0

Có, 'New-ScriptBlockCallback' là chính xác những gì tôi có trong tâm trí khi tôi đề cập bạn sẽ có câu trả lời :) – stej

+0

tuyệt vời, bây giờ tôi đã quay lại câu hỏi và Tôi hoàn toàn không biết câu trả lời nào chấp nhận :) –

+0

có phương thức 'Add-Type'! và ở đây tôi đã cố gắng phát ra IL cần thiết để thiết lập Runspace. –

6

Tôi thấy rằng bạn sử dụng cuộc gọi không đồng bộ để bạn có thể hiển thị tiến trình. Sau đó, bạn có thể sử dụng mô-đun BitsTransfer cho điều đó. Nó cho thấy sự tiến bộ theo mặc định:

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

Nếu bạn muốn chuyển file ở chế độ nền, bạn có thể sử dụng một cái gì đó như thế này:

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

Các thông số quan trọng là -async. Nó bắt đầu truyền trong nền. Tôi đã không tìm thấy bất kỳ sự kiện nào được kích hoạt bởi quá trình chuyển, vì vậy tôi truy vấn công việc mỗi giây để báo cáo trạng thái qua đối tượng Timers.Timer.

Tuy nhiên, với giải pháp này, cần phải hủy đăng ký sự kiện và hủy hẹn giờ. Một số thời gian trước, tôi đã có vấn đề với unregistering trong scriptblock thông qua như -Action (nó có thể là trong chi nhánh if), vì vậy tôi unregister sự kiện trong lệnh riêng biệt.


Tôi nghĩ @oising (x0n) có một số giải pháp trên blog của anh ấy. Anh ấy sẽ nói với bạn rằng hy vọng và đó sẽ là câu trả lời cho câu hỏi của bạn.

+0

Cảm ơn, đây là thiên tài thuần túy. Tốt hơn nhiều so với giải pháp của tôi. Bây giờ tôi sẽ chờ đợi một chút để xem có ai trả lời phần Runspace trước khi chấp nhận (chỉ tò mò thỏa thuận với nó là gì). –

+0

Tôi sẽ là một lựa chọn khác chỉ dành cho cảm hứng :) – stej

+0

Đã giải quyết phần Runspace. – x0n