Improve a progress pane performance (one thread) (#2822)

Showing a progress bar is handled on the pipeline thread only. But not every "WriteProgress" will trigger the "_progPane.Show" to be called. Instead, a timer is used to set an "update" flag that will be checked in "HandleIncomingProgressRecord" to see whether "_progPane.Show" should be called. The timer fires every 200 ms. I test intervals from 50 ms to 2000 ms and I didn't measure much difference with 100 ms and 200 ms, so 200 ms is chosen to make it more cost-efficient.
This commit is contained in:
Ilya authored and Dongbo Wang committed 2017-03-17 14:14:15 -07:00
1 parent 3a21d4c3df
commit 915dfcd0d9
1 file changed
+59 -7
@@ -7,6 +7,7 @@ Copyright (c) Microsoft Corporation. All rights reserved.
using System;
using System.Management.Automation;
using Dbg = System.Management.Automation.Diagnostics;
using System.Threading;
namespace Microsoft.PowerShell
@@ -28,14 +29,30 @@ namespace Microsoft.PowerShell
// destroy the data structures representing outstanding progress records
// take down and destroy the progress display
if (_progPane != null)
// If we have multiple runspaces on the host then any finished pipeline in any runspace will lead to call 'ResetProgress'
// so we need the lock
lock (_instanceLock)
{
Dbg.Assert(_pendingProgress != null, "How can you have a progress pane and no backing data structure?");
if (_progPaneUpdateTimer != null)
{
// Stop update a progress pane and destroy timer
_progPaneUpdateTimer.Dispose();
_progPaneUpdateTimer = null;
}
// We don't set 'progPaneUpdateFlag = 0' here, because:
// 1. According to MSDN, the timer callback can occur after the Dispose() method has been called.
// So we cannot guarantee the flag is truly set to 0.
// 2. When creating a new timer in 'HandleIncomingProgressRecord', we will set the flag to 1 anyway
_progPane.Hide();
_progPane = null;
if (_progPane != null)
{
Dbg.Assert(_pendingProgress != null, "How can you have a progress pane and no backing data structure?");
_progPane.Hide();
_progPane = null;
}
_pendingProgress = null;
}
_pendingProgress = null;
}
@@ -64,15 +81,46 @@ namespace Microsoft.PowerShell
if (_progPane == null)
{
// This is the first time we've received a progress record. Create a progress pane, then show it.
// This is the first time we've received a progress record
// Create a progress pane
// Set up a update flag
// Create a timer for updating the flag
_progPane = new ProgressPane(this);
if (_progPaneUpdateTimer == null)
{
// Show a progress pane at the first time we've received a progress record
progPaneUpdateFlag = 1;
// The timer will be auto restarted every 'UpdateTimerThreshold' ms
_progPaneUpdateTimer = new Timer( new TimerCallback(ProgressPaneUpdateTimerElapsed), null, UpdateTimerThreshold, UpdateTimerThreshold);
}
}
if (Interlocked.CompareExchange(ref progPaneUpdateFlag, 0, 1) == 1 || record.RecordType == ProgressRecordType.Completed)
{
// Update the progress pane only when the timer set up the update flag or WriteProgress is completed.
// As a result, we do not block WriteProgress and whole script and eliminate unnecessary console locks and updates.
_progPane.Show(_pendingProgress);
}
_progPane.Show(_pendingProgress);
}
/// <summary>
///
/// TimerCallback for '_progPaneUpdateTimer' to update 'progPaneUpdateFlag'
///
/// </summary>
private
void
ProgressPaneUpdateTimerElapsed(object sender)
{
Interlocked.CompareExchange(ref progPaneUpdateFlag, 1, 0);
}
private
void
PreWrite()
@@ -166,6 +214,10 @@ namespace Microsoft.PowerShell
private ProgressPane _progPane = null;
private PendingProgress _pendingProgress = null;
// The timer set up 'progPaneUpdateFlag' every 'UpdateTimerThreshold' milliseconds to update 'ProgressPane'
private Timer _progPaneUpdateTimer = null;
private const int UpdateTimerThreshold = 200;
private int progPaneUpdateFlag = 0;
}
} // namespace