Полоса выполнения в веб-клиенте

99
14

Итак, у меня есть сценарий, который показывает "Прогресс загрузки" с FTP. Я просто пробую много способов решить эту задачу. Один из выводов заключался в том, что cmdlet Register-ObjectEvent - действительно плохая идея. Асинхронное событие довольно слабо поддерживается в Powershell...
И я остановился там -

$webclient.add_DownloadProgressChanged([System.Net.DownloadProgressChangedEventHandler]$webclient_DownloadProgressChanged )
....
$webclient_DownloadProgressChanged = {
param([System.Net.DownloadProgressChangedEventArgs]$Global:e)
$progressbaroverlay1.value=$e.ProgressPercentage
....
}


И все в этом скроме прекрасно работает, но вы можете понять, что я сделал это для одного файла.
Но потом я начал думать: как я могу загрузить несколько файлов одновременно и показать их в одной строке выполнения? Итак, у кого-то есть отличные идеи? Или лучший способ решить эту задачу?

PS

WebClient может загружать только один файл за раз.

Конечно, я это знаю.

спросил(а) 2017-06-01T00:46:00+03:00 3 года, 3 месяца назад
1
Решение
89

Вы можете использовать Асинхронную загрузку модуля BitsTransfer. https://technet.microsoft.com/en-us/library/dd819420.aspx

Пример кода, показывающий общий процесс из 3-х файлов, вы указываете равный массив URL-адресов и мест для загрузки, вы можете сделать это по своему вкусу, например обработку исключений и т.д.

Import-Module BitsTransfer
[string[]]$url = @();
$url += 'https://www.samba.org/ftp/talloc/talloc-2.1.6.tar.gz';
$url += 'https://e.thumbs.redditmedia.com/pF525auqxnTG-FFj.png';
$url += 'http://bchavez.bitarmory.com/Skins/DayDreaming/images/bg-header.gif';

[string[]]$destination = @();
$destination += 'C:\Downloads\talloc-2.1.6.tar.gz';
$destination += 'C:\Downloads\pF525auqxnTG-FFj.png';
$destination += 'C:\Downloads\bg-header.gif';

$result = Start-BitsTransfer -Source $url -Destination $destination -TransferType Download -Asynchronous
$downloadsFinished = $false;
While ($downloadsFinished -ne $true) {
sleep 1
$jobstate = $result.JobState;
if($jobstate.ToString() -eq 'Transferred') { $downloadsFinished = $true }
$percentComplete = ($result.BytesTransferred / $result.BytesTotal) * 100
Write-Progress -Activity ('Downloading' + $result.FilesTotal + ' files') -PercentComplete $percentComplete
}

ответил(а) 2017-06-01T02:04:00+03:00 3 года, 3 месяца назад
41

Я придумал такой же подход Scriptblock Create, но я использовал Register-ObjectEvent с большим или меньшим успехом. Загрузка Async выполняется в качестве фоновых заданий и использует события для передачи своего прогресса обратно в основной сценарий.

$Progress = @{}

$Isos = 'https://cdimage.debian.org/debian-cd/current/i386/iso-cd/debian-8.8.0-i386-CD-1.iso',
'https://cdimage.debian.org/debian-cd/current/i386/iso-cd/debian-8.8.0-i386-CD-2.iso'

$Count = 0
$WebClients = $Isos | ForEach-Object {

$w = New-Object System.Net.WebClient

$null = Register-ObjectEvent -InputObject $w -EventName DownloadProgressChanged -Action ([scriptblock]::Create(
"'$Percent = 100 * '$eventargs.BytesReceived / '$eventargs.TotalBytesToReceive; '$null = New-Event -SourceIdentifier MyDownloadUpdate -MessageData @($count,'$Percent)"
))
$w.DownloadFileAsync($_, "C:\PATH_TO_DOWNLOAD_FOLDER\$count.iso")

$Count = $Count + 1
$w
}

$event = Register-EngineEvent -SourceIdentifier MyDownloadUpdate -Action {
$progress[$event.MessageData[0]] = $event.MessageData[1]
}

$Timer = New-Object System.Timers.Timer
Register-ObjectEvent -InputObject $Timer -EventName Elapsed -Action {
if ($Progress.Values.Count -gt 0)
{
$PercentComplete = 100 * ($Progress.values | Measure-Object -Sum | Select-Object -ExpandProperty Sum) / $Progress.Values.Count
Write-Progress -Activity "Download Progress" -PercentComplete $PercentComplete
}
}

$timer.Interval = 100
$timer.AutoReset = $true
$timer.Start()

Упражнение для читателя о том, как сказать, что загрузка завершена.

ответил(а) 2017-06-01T20:15:00+03:00 3 года, 3 месяца назад
40

Я вижу две возможные концепции для этого:

Создать (с [scriptblock]::Create) анонимную функцию "на лету", что-то вроде:
$Id = 0
... | ForEach {
$webclient[$Id].add_DownloadProgressChanged([System.Net.DownloadProgressChangedEventHandler]{[scriptblock]::Create("
....
'$webclient_DownloadProgressChanged = {
param([System.Net.DownloadProgressChangedEventArgs]'$e)
'$Global:ProgressPercentage[$Id]='$e.ProgressPercentage
'$progressbaroverlay1.value=('$Global:ProgressPercentage | Measure-Object -Average).Average
....
"
$Id++
})
}

Обратите внимание, что в этой идее вам нужно запретить все, кроме $Id прямо интерпретироваться с помощью обратного хода.

Или, если функция становится слишком большой для чтения, упростите [ScriptBlock]:


[ScriptBlock]::Create("param('$e); webclient_DownloadProgressChanged $Id '$e")

и вызовите глобальную функцию:

$Global:webclient_DownloadProgressChanged($Id, $e) {
$Global:ProgressPercentage[$Id]=$e.ProgressPercentage
$progressbaroverlay1.value=($Global:ProgressPercentage | Measure-Object -Average).Average
}
Создайте собственные собственные рабочие фона (потоки):

Пример: PowerShell: действие события события с незавершенной формой

    В основном потоке создайте свой пользовательский интерфейс с индикаторами выполнения Для каждой загрузки FTP:
      Создайте общий (скрытый) элемент управления Windows (например .TextBox[$Id]). Запустите новый фоновый рабочий и передайте связанный элемент управления, например:
      $SyncHash = [hashtable]::Synchronized(@{TextBox = $TextBox[$Id]})
    Обновите общий $SyncHash.TextBox.Text = из WebWorker (ов) Захватите события (например .Add_TextChanged) на каждом .TextBox[$Id] в главном потоке Обновите свои полосы выполнения соответственно на основе среднего значения, переданного в каждом .TextBox[$Id].Text

ответил(а) 2017-06-01T14:57:00+03:00 3 года, 3 месяца назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема