问题
I have a program that displays a UI
, allows the user to pick virtual machine names obtained by querying the Xen
pool master server, and then creates snapshots for the selected virtual machines. I want the snapshots to be created in the background so I can keep the UI
responsive and update the UI
with progress as each snapshot is created.
Originally, I connected to the Xen
pool master server and then executed the Xen
create snapshot cmdlet once per selected VM in the UI
thread. As such, the UI
became unresponsive.
Next, I connected to the Xen
pool master server and then did a start-job
(background job
) once per VM to create the VM's snapshot. This failed because the Xen
session created in the UI
thread could not be passed to the background job
(The content of the session variable makes it into the block, but the Xen Connect
cmdlet in the block returns a Could not find open sessions to any XenServers
error).
Then, I moved connecting to the Xen
pool master server into the background job
. This slowed operations because the making the connection takes a few seconds and was being done once for each VM. However, the UI
remained responsive and I was able to use job completion data to update the UI
.
How can I keep the UI
responsive, update the UI
with progress as each snapshot is created, and not be forced to connect to the server once per snapshot?
回答1:
Update
This solution does allow updates to be obtained in the the UI thread as the background job runs, but it does not keep the UI responsive. See my response to Rohin below.
The solution was to move the entire loop into the background job
and use the Write-Progress
cmdlet to report progress on the UI
. Credit for using the Write-Progress
cmdlet in this scenario goes to Ryan
Here is a simple demo illustrating all the needed pieces
cls
## Job code
$ScriptBlockCode =
{
param($items)
$retObj = @{}
try
{
$error.clear()
for ($i = 0; $i -lt $items.Count; $i++)
{
#wait to simulate the time it takes to do work on item $i
start-sleep -Seconds (get-random -Minimum 2 -Maximum 6)
# Use Write-Progress to report the completion of each item for as long as the job runs (Can be used to updage the UI)
# Use the -Completed argument to suppress displaying the progress bar
Write-Progress -Id ($i+1) -Activity ("For UI Update: " + $items[$i] + " processing complete") -Status "Reporting" -PercentComplete ((($i+1)/$items.Count)*100) -Completed
#Use a hashtable to report the status of each job. To be used in the calling code after the job status is no longer Running
#simulate some items passing, some failing
if ((get-random -Minimum 0 -Maximum 2) -eq 0)
{
$itemRet = [PSCustomObject]@{status="FAIL";details="some error description"}
}
else
{
$itemRet = [PSCustomObject]@{status="PASS";details=""}
}
$retObj.Add($items[$i],$itemRet)
}
return $retObj
}
catch
{
$itemRet = [PSCustomObject]@{status="ERROR";details=$error}
$retObj.Add("FATAL",$itemRet)
return $retObj
}
}
cls
#clean up before starting
Get-Job -Name "UniqueJobName" -ErrorAction SilentlyContinue | Stop-Job
Get-Job -Name "UniqueJobName" -ErrorAction SilentlyContinue | Remove-Job
#simulate 5 pieces of work
$items = @("Item A", "Item B", "Item C", "Item D", "Item E")
$job = Start-Job -Name "UniqueJobName" -ScriptBlock $ScriptBlockCode -ArgumentList ($items)
#loop and update UI until job is done
$lastActivityId = -99
While ($job.State -eq "Running")
{
$child = $job.ChildJobs[0]
#update the UI only if progress has started and the ActivityId has not already been reported on and the Progress report is one I care about
if ($child.Progress.Count -gt 0 -and $child.Progress[$child.Progress.Count - 1].ActivityId -ne $lastActivityId -and ($child.Progress[$child.Progress.Count - 1]).StatusDescription -eq "Reporting")
{
write-host "=============================="
write-host "in progress updates"
write-host "=============================="
#use the progress properties, i.e., RecordType and PercentComplete to update the UI
$child.Progress[$child.Progress.Count - 1]
#store this Id so we can ignore progress until Id changes
$lastActivityId = $child.Progress[$child.Progress.Count - 1].ActivityId
}
#period at which the UI is updated
start-sleep -milliseconds 250
}
$retObj = Receive-Job -Name "UniqueJobName"
write-host "=============================="
write-host "receive job"
write-host "=============================="
# Because the job may finish before progress is captured
# for each item, use the returned values to update the UI one last time
foreach ($key in $retObj.GetEnumerator())
{
"retObj=" + $key.name + " " + $key.Value.status + " " + $key.Value.details
}
#cleanup
Get-Job -Name "UniqueJobName" | Stop-Job
Get-Job -Name "UniqueJobName" | Remove-Job
Sample output
==============================
in progress updates
==============================
ActivityId : 1
ParentActivityId : -1
Activity : For UI Update: Item A processing complete
StatusDescription : Reporting
CurrentOperation :
PercentComplete : 20
SecondsRemaining : -1
RecordType : Completed
==============================
in progress updates
==============================
ActivityId : 2
ParentActivityId : -1
Activity : For UI Update: Item B processing complete
StatusDescription : Reporting
CurrentOperation :
PercentComplete : 40
SecondsRemaining : -1
RecordType : Completed
==============================
in progress updates
==============================
ActivityId : 3
ParentActivityId : -1
Activity : For UI Update: Item C processing complete
StatusDescription : Reporting
CurrentOperation :
PercentComplete : 60
SecondsRemaining : -1
RecordType : Completed
==============================
in progress updates
==============================
ActivityId : 4
ParentActivityId : -1
Activity : For UI Update: Item D processing complete
StatusDescription : Reporting
CurrentOperation :
PercentComplete : 80
SecondsRemaining : -1
RecordType : Completed
==============================
receive job
==============================
retObj=Item D PASS
retObj=Item E PASS
retObj=Item A FAIL some error description
retObj=Item B FAIL some error description
retObj=Item C PASS
来源:https://stackoverflow.com/questions/52689603/how-can-i-keep-the-ui-responsive-and-updated-with-progress-while-all-the-work-is