从事件处理程序报告Powershell进度

时间:2016-07-15 22:22:59

标签: c# .net powershell

我在C#中编写了一个cmdlet,它充当重/长时间运行同步操作的包装器。该方法(其他人的代码)通过事件处理程序报告在此长时间运行操作期间的百分比进度,并且我想将它们连接到powershell的标准WriteProgress方法以获得漂亮打印的进度条。但是,我收到以下错误消息:

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.

这是我的代码:

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);
}

任何人都能发现我做错了吗?

更新:看起来事件处理程序是从与ProcessRecord()不同的线程触发的。如何将我需要的信息返回到与ProcessRecord()相同的帖子中?

1 个答案:

答案 0 :(得分:1)

您需要手动将ProgressChanged事件处理程序封送回PowerShell管道线程。它可以通过应用生产者 - 消费者模式来完成,其中ProgressChanged事件处理程序将是生产者,而PowerShell管道线程中的事件循环将是消费者。它可以通过.NET Framework 4.0中引入的BlockingCollection<T>支持轻松实现:

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();
}