How can I keep the UI responsive and updated with progress while all the work is done in a background job on the local computer?

冷暖自知 提交于 2019-12-14 03:03:22

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!