forked from PowerShell/PowerShell
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathConsoleHostUserInterfaceProgress.cs
More file actions
225 lines (176 loc) · 6.27 KB
/
Copy pathConsoleHostUserInterfaceProgress.cs
File metadata and controls
225 lines (176 loc) · 6.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/********************************************************************++
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
{
internal partial
class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInterface
{
/// <summary>
///
/// Called at the end of a prompt loop to take down any progress display that might have appeared and purge any
/// outstanding progress activity state.
///
/// </summary>
internal
void
ResetProgress()
{
// destroy the data structures representing outstanding progress records
// take down and destroy the progress display
// 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)
{
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
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;
}
}
/// <summary>
///
/// Invoked by ConsoleHostUserInterface.WriteProgress to update the set of outstanding activities for which
/// ProgressRecords have been received.
///
/// </summary>
private
void
HandleIncomingProgressRecord(Int64 sourceId, ProgressRecord record)
{
Dbg.Assert(record != null, "record should not be null");
if (_pendingProgress == null)
{
Dbg.Assert(_progPane == null, "If there is no data struct, there shouldn't be a pane, either.");
_pendingProgress = new PendingProgress();
}
_pendingProgress.Update(sourceId, record);
if (_progPane == null)
{
// 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);
}
}
/// <summary>
///
/// TimerCallback for '_progPaneUpdateTimer' to update 'progPaneUpdateFlag'
///
/// </summary>
private
void
ProgressPaneUpdateTimerElapsed(object sender)
{
Interlocked.CompareExchange(ref progPaneUpdateFlag, 1, 0);
}
private
void
PreWrite()
{
if (_progPane != null)
{
_progPane.Hide();
}
}
private
void
PostWrite()
{
if (_progPane != null)
{
_progPane.Show();
}
}
private
void
PostWrite(string value)
{
PostWrite();
if (_parent.IsTranscribing)
{
try
{
_parent.WriteToTranscript(value);
}
catch (Exception)
{
_parent.IsTranscribing = false;
}
}
}
private
void
PreRead()
{
if (_progPane != null)
{
_progPane.Hide();
}
}
private
void
PostRead()
{
if (_progPane != null)
{
_progPane.Show();
}
}
private
void
PostRead(string value)
{
PostRead();
if (_parent.IsTranscribing)
{
try
{
// Reads always terminate with the enter key, so add that.
_parent.WriteToTranscript(value + Crlf);
}
catch (Exception)
{
_parent.IsTranscribing = false;
}
}
}
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