I've written a cmdlet in C# that acts as a wrapper for a heavy/long-running synchronous operation. The method (someone else' code) reports percentage progress during this long-running operation via event handlers, and I'd like to hook these up to powershell's standard WriteProgress method to get the pretty-printed progress bar. However, I'm getting the following error message:
The WriteObject and WriteError methods cannot be called from outside the overrides of the BeginProcessing, ProcessRecord, and EndProcessing methods, and they can only be called from within the same thread.
Here's my code:
overrride void ProcessRecord()
{
LongRunningOperation op = new LongRunningOperation();
op.ProgressChanged += ProgressUpdate;
op.Execute();
op.ProgressChanged -= ProgressUpdate;
}
void ProgressUpdate(object sender, ProgressChangeEventArgs e)
{
ProgressRecord progress = new ProgressRecord(activityId: 1, activity: "Moving data", statusDescription: "Current operation");
progress.PercentComplete = e.ProgressPercentage;
WriteProgress(progress);
}
Anyone able to spot what I'm doing wrong?
Update: Looks like the event handler is being triggered from a different thread than ProcessRecord(). How can I get the information I need back into the same thread as ProcessRecord()?
You need to manually marshal ProgressChanged event handler back to PowerShell pipeline thread. It can be done by applying producer–consumer pattern where ProgressChanged event handler will be producer and event loop in PowerShell pipeline thread will be consumer. It can be easy implemented with support of BlockingCollection<T> introduced in .NET Framework 4.0:
overrride void ProcessRecord() {
Task longRunningOperation;
using(BlockingCollection<ProgressRecord> queue = new BlockingCollection<ProgressRecord>()) {
//offload LongRunningOperation to different thread to keep control on PowerShell pipeline thread
longRunningOperation=Task.Run(() => {
try {
//replace EventHandler<ProgressChangeEventArgs> with ProgressChanged type
EventHandler<ProgressChangeEventArgs> handler =
//implemented as anonymous method to capture queue local variable
(object sender, ProgressChangeEventArgs e) => {
ProgressRecord progress = new ProgressRecord(activityId: 1, activity: "Moving data", statusDescription: "Current operation");
progress.PercentComplete = e.ProgressPercentage;
//queue ProgressRecord for processing in PowerShell pipeline thread
queue.Add(progress);
}
LongRunningOperation op = new LongRunningOperation();
op.ProgressChanged += handler;
op.Execute();
op.ProgressChanged -= handler;
} finally {
queue.CompleteAdding();
}
});
//event loop
for(;;) {
ProgressRecord progress;
if(!queue.TryTake(out progress, Timeout.Infinite)) {
break;
}
WriteProgress(progress);
}
}
//get any exception from LongRunningOperation
longRunningOperation.GetAwaiter().GetResult();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With