-
|
I had the requirement to invoke async business logic from PSCmdlets ProcessRecords. A solution that worked for me so far was to create a base class for my cmdlets that provides an I don't believe I'm the first one having the problem, but I couldn't find another solution so far. Does anybody know other approaches to this problem? public class AsyncPSCmdlet : PSCmdlet
{
#region Dispatch into main thread
private readonly BlockingCollection<Action> dispatcherQueue = new();
private readonly int ownerThreadId = Thread.CurrentThread.ManagedThreadId;
/// <summary>
/// Invokes the <paramref name="action"/> in the dispatcher thread
/// </summary>
public void Dispatch(Action action)
{
if (this.ownerThreadId == Thread.CurrentThread.ManagedThreadId)
action?.Invoke();
else if (action is { } notNull)
this.dispatcherQueue.Add(notNull);
}
#endregion Dispatch into main thread
#region Cancel processing records
/// <summary>
/// Cancellation toke source associated to the Ctrl-C event
/// </summary>
protected CancellationTokenSource ControlCTokenSource { get; } = new();
/// <summary>
/// Propagates Ctrl-C to async operations
/// </summary>
protected override void StopProcessing() => this.ControlCTokenSource.Cancel();
protected override void EndProcessing() => this.ControlCTokenSource.Dispose();
#endregion Cancel processing records
#region Adapts calls to sync ProcessRecord to async ProcessRecordAsync
protected override void ProcessRecord()
{
var cancellationToken = this.ControlCTokenSource.Token;
try
{
// run the async operation from this STA/foreground thread at the thread pool.
var processRecordInBackground = Task
.Run(async () => await this.ProcessRecordAsync(cancellationToken), cancellationToken)
.ContinueWith(_ => this.dispatcherQueue.CompleteAdding());
// execute dispatched actions in STA/foreground thread
foreach (var action in this.dispatcherQueue.GetConsumingEnumerable(cancellationToken))
action?.Invoke();
// await the finalization of the BG task
processRecordInBackground.GetAwaiter().GetResult();
}
catch (OperationCanceledException)
{
}
}
/// <summary>
/// Override instead of <see cref="ProcessRecord"/> to implement async processing of records
/// </summary>
protected virtual Task ProcessRecordAsync(CancellationToken cancellationToken) => Task.CompletedTask;
#endregion Adapts calls to sync ProcessRecord to async ProcessRecordAsync
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
|
Yea that's essentially the approach I've done in the past. Here's an example with a few more methods added to support other streams and stuff like |
Beta Was this translation helpful? Give feedback.
Yea that's essentially the approach I've done in the past. Here's an example with a few more methods added to support other streams and stuff like
ShouldProcesshttps://github.com/jborean93/PowerShell-OpenAuthenticode/blob/main/src/OpenAuthenticode/AsyncPSCmdlet.cs. TheCancelTokencan be removed if you are targeting 7.6+ as it introduces a new PipelineStopToken on thePSCmdletinstance that is automatically tied to stopping the engine.