Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
245 views
in Technique[技术] by (71.8m points)

powershell - 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?

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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 

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...