-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathIdle.cs
392 lines (355 loc) · 14.4 KB
/
Idle.cs
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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
/// Idle.cs ActivistInvestor / Tony Tanzillo
///
/// Distributed under the terms of the MIT license
///
/// Utilities that aid in the use of AutoCAD's
/// Application.Idle event in various ways.
///
/// Original source location:
///
/// https://github.com/ActivistInvestor/AcMgdUtility/blob/main/Idle.cs
///
/// 05/12/24: Major refactoring
///
/// This unit was refactored to address a shortcoming
/// in the original design, which did not permit custom
/// state to be associated with each handler to the Idle
/// event. The refactored code uses a type derived from
/// TaskCompletionSource<object>, that can be further-
/// specialized to include custom state associated with
/// each instance.
///
/// See the IdleCompletionSource class for details.
///
/// O5/17/24: Implementation of the IdleAwaiter class
namespace Autodesk.AutoCAD.ApplicationServices.AsyncHelpers
{
/// <summary>
/// Provides support for asynchronous operations
/// that are driven by the Application.Idle event.
/// </summary>
public static class Idle
{
/// <summary>
/// Wraps a handler for the Application.Idle event and
/// allows the body of the event handler to be expressed
/// as code that follows an awaited call to this method.
///
/// Hence, awaited calls to this method will not return
/// until the next Idle event is raised and optionally,
/// there is an active document that optionally, is in
/// a quiescent state.
///
/// The default values for the optional arguments require
/// an active document that does not need to be quiescent.
/// Callers can pass false to indicate this method should
/// return on the first Idle event, regardless of whether
/// there is an active document or not.
///
/// Usage:
///
/// public static async void MyMethod()
/// {
/// var DocMgr = Application.DocumentManager;
///
/// // wait for the next Idle event to be raised:
///
/// await Idle.WaitAsync();
///
/// // Code appearing here will not run until
/// // the next Idle event is raised, and there
/// // is an active document. The following code
/// // can assume that MdiActiveDocument is not
/// // null.
///
/// var doc = DocMgr.MdiActiveDocument;
/// doc.Editor.WriteMessage("An Idle event was raised.");
/// }
///
/// <remarks>
/// if quiescenceRequired is true, documentRequired is
/// not evaluated and is implicitly true.
/// If there is no active document, quiescenceRequired
/// is not evaluated and is implicitly false.
/// </remarks>
/// </summary>
/// <param name="quiescenceRequired">A value indicating
/// if the method should wait until there is an active
/// document, and it is in a quiescent state.</param>
/// <param name="documentRequired">A value indicating if the
/// method should continue to wait until there is an active
/// document. If this value is true and there is no active
/// document when the Idle event is raised, the method will
/// not return until an Idle event is raised and there is
/// an active document.</param>
///
/// <returns>A Task representing the asynchronous operation</returns>
/// <exception cref="ArgumentNullException"></exception>
public static Task WaitAsync(
bool quiescenceRequired = false,
bool documentRequired = true)
{
return new IdleCompletionSource(quiescenceRequired,
documentRequired).Task;
}
/// <summary>
/// Simple, no-frills means of asynchrnously waiting
/// until the next Application.Idle event is raised,
/// with no conditions.
/// </summary>
public static Task WaitForIdle()
{
return new IdleCompletionSource(false, false).Task;
}
/// <summary>
/// Waits until there is an active document
/// </summary>
public static Task WaitForDocument()
{
return new IdleCompletionSource(false, true).Task;
}
/// <summary>
/// Waits until there is a quiescent active document
/// </summary>
public static Task WaitForQuiescentDocument()
{
return new IdleCompletionSource(true, true).Task;
}
/// <summary>
/// Takes a predicate and asynchronously waits until the
/// predicate evaluates to true. The predicate is evaluated
/// each time the Idle event is raised and optionally, when
/// this method is called. This method returns when a call
/// to the predicate returns true.
///
/// <remarks>
/// The behavior of this API when called from the document
/// execution context is undefined.
/// </remarks>
/// </summary>
/// <param name="waitForIdle">A value indicating if the given
/// predicate should not be evaluated until the next idle event
/// is raised. If this value is false, the predicate will be
/// evaluated immediately upon calling this method, before it
/// enters an asynchronous wait state, and if the predicate
/// evaluates to true, the method returns immediately. If this
/// value is true, the predicate is not evaluated until the
/// next Idle event is raised. The default value is false</param>
/// <param name="predicate">A predicate that returns a value
/// indicating if this method should return, or continue to
/// wait for additional Idle events to be raised. The predicate
/// is evaluated each time the Idle event is raised, until it
/// returns true, at which point this method returns.</param>
/// <returns>A Task representing the asynchronous operation</returns>
/// <exception cref="ArgumentNullException"></exception>
public static Task WaitUntil(bool waitForIdle, Func<bool> predicate)
{
if(predicate == null)
throw new ArgumentNullException(nameof(predicate));
if(!waitForIdle && predicate())
return Task.CompletedTask;
else
return new IdleCompletionSource(predicate).Task;
}
/// <summary>
/// Overload of WaitUntil() that passes a default value
/// of false for the waitForIdle argument, causing it to
/// evaluate the predicate immediately, and short-circuit
/// the asynchronous wait if the predicate returns true.
/// </summary>
public static Task WaitUntil(Func<bool> predicate)
{
return WaitUntil(false, predicate);
}
/// <summary>
/// An overloaded version of WaitUntil() that requires
/// a predicate that takes a Document as an argument.
///
/// Waits until there is an active document, and the
/// supplied predicate returns true. The predicate is
/// passed the Document that is active at the point
/// when the predicate is invoked. That may not be the
/// same document that was active when this method was
/// called.
///
/// Usage:
/// <code>
///
/// public static async void MyMethod()
/// {
/// var DocMgr = Application.DocumentManager;
///
/// // Waits until there is an active document
/// // that is in a quiescent state:
///
/// await Idle.WaitUntil(doc => doc.Editor.IsQuiescent);
///
/// // Code appearing here will not run until
/// // the next Idle event is raised; there is
/// // an active document; and that document
/// // is in a quiescent state.
///
/// var doc = DocMgr.MdiActiveDocument;
/// doc.Editor.WriteMessage("The Drawing Editor is quiescent");
/// }
///
/// </code>
/// <remarks>
/// If this method is called at startup (such as from an
/// IExtensionApplication.Initialize() method), it can use
/// the default value of documentRequired (true), to wait
/// for an active document before evaluating the predicate.
///
/// The behavior of this method if called from the document
/// execution context is undefined.
///
/// </remarks>
/// </summary>
/// <param name="predicate">A method taking a Document
/// as its argument, that returns a value indicating if
/// this method should return, or continue to wait.
/// </param>
/// <param name="waitForIdle">A value indicating if the given
/// predicate should be evaluated immediately when this method
/// is called. If this value is false, the predicate will be
/// evaluated when this method is called, before entering into
/// an asynchronous wait state. If the predicate evaluates to
/// true, the method returns immediately without entering into
/// the asynchronous wait state. If this value is true, the
/// predicate is not evaluated until the first Idle event has
/// been raised. The default value is true.</param>
/// <returns>A Task representing the asynchronous operation</returns>
/// <exception cref="ArgumentNullException"></exception>
public static Task WaitUntil(Func<Document, bool> predicate,
bool waitForIdle = true)
{
if(predicate == null)
throw new ArgumentNullException(nameof(predicate));
Document doc = ActiveDocument;
if(!waitForIdle && doc != null && predicate(doc))
return Task.CompletedTask;
else
return new IdleCompletionSource(() =>
ActiveDocument != null && predicate(ActiveDocument)).Task;
}
/// <summary>
/// Indicates if an operation can execute based on
/// specified conditions.
/// <param name="document">A value indicating if an
/// active document is required to execute the operation</param>
/// <param name="quiescent">A value indicating if a
/// quiescent active document is required to execute
/// the operation</param>
/// <remarks>
/// if quiescent is true, document is not evaluated
/// and is effectively true.
/// If there is no active document, quiescent is not
/// evaluated and is effectively false.
/// </remarks>
/// </summary>
public static bool CanInvoke(bool quiescent = false, bool document = true)
{
document |= quiescent;
Document doc = ActiveDocument;
return doc == null ? !document
: !quiescent || doc.Editor.IsQuiescent;
}
static readonly DocumentCollection docs = Application.DocumentManager;
static Document ActiveDocument => Application.DocumentManager.MdiActiveDocument;
}
/// <summary>
/// Can be used in place of the base type to simplify
/// asynchronous wait-for-idle operations, and perform
/// custom conditional testing to determine if waiting
/// should continue, and to associate custom state with
/// each instance if needed.
/// </summary>
public class IdleCompletionSource : TaskCompletionSource<object>
{
Func<bool> predicate = null;
public IdleCompletionSource(
bool quiescentRequired = false,
bool documentRequired = true)
{
predicate = () => Idle.CanInvoke(documentRequired, quiescentRequired);
Application.Idle += idle;
}
public IdleCompletionSource(Func<bool> predicate)
{
if(predicate == null)
throw new ArgumentNullException(nameof(predicate));
this.predicate = predicate;
Application.Idle += idle;
}
/// <summary>
/// Can be overridden to perform custom/additional
/// conditional testing to determine if asynchronous
/// wait should end.
///
/// This default implementation for an instance
/// created with the default constructor signals
/// completion if there is an active document,
/// quiescent or not.
/// </summary>
protected virtual bool IsCompleted
{
get
{
return predicate();
}
}
private void idle(object sender, EventArgs e)
{
if(IsCompleted)
{
Application.Idle -= idle;
TrySetResult(null);
}
}
}
/// <summary>
/// An alternative means of doing what the methods of the
/// Idle.WaitForIdle() method does, with greater control
/// over how awaited continuation statements execute, but
/// lacking any means to conditionally wait.
///
/// If there are multiple actions queued up, only one of
/// them is dequeued and invoked on each call to the Idle
/// event handler, so as to not impair UI responsiveness.
/// </summary>
public struct IdleAwaiter : INotifyCompletion
{
static ConcurrentQueue<Action> actions = new ConcurrentQueue<Action>();
static void idle(object sender, EventArgs e)
{
Action action = null;
bool flag = actions.Count > 0;
if(flag && actions.TryDequeue(out action) && action != null)
{
if(actions.Count == 0)
Application.Idle -= idle;
SynchronizationContext.Current.Post(
state => ((Action)state)(), action);
}
}
public static IdleAwaiter WaitOne()
{
return new IdleAwaiter();
}
public IdleAwaiter GetAwaiter() { return this; }
public bool IsCompleted { get { return false; } }
public void GetResult() { }
public void OnCompleted(Action continuation)
{
bool flag = actions.Count == 0;
actions.Enqueue(continuation);
if(flag && actions.Count > 0)
Application.Idle += idle;
}
}
}