private/runtime/AsyncCommandRuntime.cs
namespace Microsoft.Rest.ClientRuntime.PowerShell { using System.Management.Automation; using System.Management.Automation.Host; using System.Threading; public class AsyncCommandRuntime : System.Management.Automation.ICommandRuntime2, System.IDisposable { private ICommandRuntime2 originalCommandRuntime; private System.Threading.Thread originalThread; public bool AllowInteractive { get; set; } = false; public CancellationToken cancellationToken; SemaphoreSlim semaphore = new SemaphoreSlim(1, 1); ManualResetEventSlim readyToRun = new ManualResetEventSlim(false); ManualResetEventSlim completed = new ManualResetEventSlim(false); System.Action runOnMainThread; private System.Management.Automation.PSCmdlet cmdlet; public AsyncCommandRuntime(System.Management.Automation.PSCmdlet cmdlet, CancellationToken cancellationToken) { this.originalCommandRuntime = cmdlet.CommandRuntime as ICommandRuntime2; this.originalThread = System.Threading.Thread.CurrentThread; this.cancellationToken = cancellationToken; this.cmdlet = cmdlet; cmdlet.CommandRuntime = this; } public PSHost Host => this.originalCommandRuntime.Host; public PSTransactionContext CurrentPSTransaction => this.originalCommandRuntime.CurrentPSTransaction; private void CheckForInteractive() { // This is an interactive call -- if we are not on the original thread, this will only work if this was done at ACR creation time; if (!AllowInteractive) { throw new System.Exception("AsyncCommandRuntime is not configured for interactive calls"); } } private void WaitOurTurn() { // wait for our turn to play semaphore?.Wait(cancellationToken); // ensure that completed is not set completed.Reset(); } private void WaitForCompletion() { // wait for the result (or cancellation!) WaitHandle.WaitAny(new[] { cancellationToken.WaitHandle, completed?.WaitHandle }); // let go of the semaphore semaphore?.Release(); } public bool ShouldContinue(string query, string caption, bool hasSecurityImpact, ref bool yesToAll, ref bool noToAll) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { return originalCommandRuntime.ShouldContinue(query, caption, hasSecurityImpact, ref yesToAll, ref noToAll); } CheckForInteractive(); // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); bool yta = yesToAll; bool nta = noToAll; bool result = false; // set the function to run runOnMainThread = () => result = originalCommandRuntime.ShouldContinue(query, caption, hasSecurityImpact, ref yta, ref nta); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // set the output variables yesToAll = yta; noToAll = nta; return result; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public bool ShouldContinue(string query, string caption) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { return originalCommandRuntime.ShouldContinue(query, caption); } CheckForInteractive(); // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); bool result = false; // set the function to run runOnMainThread = () => result = originalCommandRuntime.ShouldContinue(query, caption); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // set the output variables return result; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public bool ShouldContinue(string query, string caption, ref bool yesToAll, ref bool noToAll) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { return originalCommandRuntime.ShouldContinue(query, caption, ref yesToAll, ref noToAll); } CheckForInteractive(); // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); bool yta = yesToAll; bool nta = noToAll; bool result = false; // set the function to run runOnMainThread = () => result = originalCommandRuntime.ShouldContinue(query, caption, ref yta, ref nta); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // set the output variables yesToAll = yta; noToAll = nta; return result; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public bool ShouldProcess(string target) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { return originalCommandRuntime.ShouldProcess(target); } CheckForInteractive(); // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); bool result = false; // set the function to run runOnMainThread = () => result = originalCommandRuntime.ShouldProcess(target); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // set the output variables return result; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public bool ShouldProcess(string target, string action) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { return originalCommandRuntime.ShouldProcess(target, action); } CheckForInteractive(); // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); bool result = false; // set the function to run runOnMainThread = () => result = originalCommandRuntime.ShouldProcess(target, action); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // set the output variables return result; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public bool ShouldProcess(string verboseDescription, string verboseWarning, string caption) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { return originalCommandRuntime.ShouldProcess(verboseDescription, verboseWarning, caption); } CheckForInteractive(); // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); bool result = false; // set the function to run runOnMainThread = () => result = originalCommandRuntime.ShouldProcess(verboseDescription, verboseWarning, caption); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // set the output variables return result; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public bool ShouldProcess(string verboseDescription, string verboseWarning, string caption, out ShouldProcessReason shouldProcessReason) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { return originalCommandRuntime.ShouldProcess(verboseDescription, verboseWarning, caption, out shouldProcessReason); } CheckForInteractive(); // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); bool result = false; ShouldProcessReason reason = ShouldProcessReason.None; // set the function to run runOnMainThread = () => result = originalCommandRuntime.ShouldProcess(verboseDescription, verboseWarning, caption, out reason); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // set the output variables shouldProcessReason = reason; return result; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public void ThrowTerminatingError(ErrorRecord errorRecord) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { originalCommandRuntime.ThrowTerminatingError(errorRecord); return; } // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); // set the function to run runOnMainThread = () => originalCommandRuntime.ThrowTerminatingError(errorRecord); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // return return; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public bool TransactionAvailable() { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { return originalCommandRuntime.TransactionAvailable(); } // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); bool result = false; // set the function to run runOnMainThread = () => result = originalCommandRuntime.TransactionAvailable(); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // set the output variables return result; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public void WriteCommandDetail(string text) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { originalCommandRuntime.WriteCommandDetail(text); return; } // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); // set the function to run runOnMainThread = () => originalCommandRuntime.WriteCommandDetail(text); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // return return; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public void WriteDebug(string text) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { originalCommandRuntime.WriteDebug(text); return; } // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); // set the function to run runOnMainThread = () => originalCommandRuntime.WriteDebug(text); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // return return; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public void WriteError(ErrorRecord errorRecord) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { originalCommandRuntime.WriteError(errorRecord); return; } // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); // set the function to run runOnMainThread = () => originalCommandRuntime.WriteError(errorRecord); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // return return; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public void WriteInformation(InformationRecord informationRecord) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { originalCommandRuntime.WriteInformation(informationRecord); return; } // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); // set the function to run runOnMainThread = () => originalCommandRuntime.WriteInformation(informationRecord); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // return return; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public void WriteObject(object sendToPipeline) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { originalCommandRuntime.WriteObject(sendToPipeline); return; } // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); // set the function to run runOnMainThread = () => originalCommandRuntime.WriteObject(sendToPipeline); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // return return; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public void WriteObject(object sendToPipeline, bool enumerateCollection) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { originalCommandRuntime.WriteObject(sendToPipeline, enumerateCollection); return; } // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); // set the function to run runOnMainThread = () => originalCommandRuntime.WriteObject(sendToPipeline, enumerateCollection); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // return return; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public void WriteProgress(ProgressRecord progressRecord) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { originalCommandRuntime.WriteProgress(progressRecord); return; } // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); // set the function to run runOnMainThread = () => originalCommandRuntime.WriteProgress(progressRecord); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // return return; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public void WriteProgress(long sourceId, ProgressRecord progressRecord) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { originalCommandRuntime.WriteProgress(sourceId, progressRecord); return; } // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); // set the function to run runOnMainThread = () => originalCommandRuntime.WriteProgress(sourceId, progressRecord); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // return return; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public void WriteVerbose(string text) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { originalCommandRuntime.WriteVerbose(text); return; } // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); // set the function to run runOnMainThread = () => originalCommandRuntime.WriteVerbose(text); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // return return; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public void WriteWarning(string text) { // if we are on the original thread, just call straight thru. if (this.originalThread == System.Threading.Thread.CurrentThread) { originalCommandRuntime.WriteWarning(text); return; } // otherwise, queue up the request and wait for the main thread to do the right thing. try { // wait for our turn to talk to the main thread WaitOurTurn(); // set the function to run runOnMainThread = () => originalCommandRuntime.WriteWarning(text); // tell the main thread to go ahead readyToRun.Set(); // wait for the result (or cancellation!) WaitForCompletion(); // return return; } catch (System.OperationCanceledException exception) { // maybe don't even worry? throw exception; } } public void Wait(System.Threading.Tasks.Task ProcessRecordAsyncTask, System.Threading.CancellationToken cancellationToken) { do { WaitHandle.WaitAny(new[] { readyToRun.WaitHandle, ((System.IAsyncResult)ProcessRecordAsyncTask).AsyncWaitHandle }); if (readyToRun.IsSet) { // reset the request for the next time readyToRun.Reset(); // run the delegate on this thread runOnMainThread(); // tell the originator everything is complete completed.Set(); } } while (!ProcessRecordAsyncTask.IsCompleted); if( ProcessRecordAsyncTask.IsFaulted ) { // don't unwrap a Aggregate Exception -- we'll lose the stack trace of the actual exception. // if( ProcessRecordAsyncTask.Exception is System.AggregateException aggregate ) { // throw aggregate.InnerException; // } throw ProcessRecordAsyncTask.Exception; } } public void Dispose() { if( cmdlet != null ) { cmdlet.CommandRuntime = this.originalCommandRuntime; cmdlet = null; } semaphore?.Dispose(); semaphore = null; readyToRun?.Dispose(); readyToRun = null; completed?.Dispose(); completed = null; } } } |