3

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()?

6
  • 1
    Are you sure that op.ProgressChanged raised in the same thread as op.Execute() called? Commented Jul 15, 2016 at 22:41
  • @PetSerAl Whelp, that appears to be problem the (a?) problem. Didn't think op.Execute() would spin up some new threads. Any idea how to resolve that? Commented Jul 15, 2016 at 23:03
  • What PowerShell and .NET version are you targeting? Commented Jul 15, 2016 at 23:06
  • PowerShell 3, .NET 4.5 Commented Jul 15, 2016 at 23:19
  • Does is make a difference if you register a lambda/delegate to the event, rather than an instance method? Commented Jul 15, 2016 at 23:31

1 Answer 1

1

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();
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.