c# - Powershell 自定义 cmdlet 在标准方法的实现之外调用 WriteVerbose

标签 c# multithreading powershell backgroundworker powershell-cmdlet

Msdn 文档对 WriteVerbose 方法说:

“此方法只能从 BeginProcessing、ProcessRecord 和 EndProcessing 方法的实现中调用,并且只能从该线程调用。如果此调用是从这些实现之外或从另一个线程进行的,则会抛出 InvalidOperationException 异常。”

但在我的测试代码中,我能够在三个标准方法之外调用该方法。但是从另一个线程调用该方法时出现异常。

我相信这里的大多数人在实现自定义 cmdlet 时都需要多线程支持。

我正在使用后台 worker 来完成简单的多线程。在我继续之前,我在 ProcessRecord 函数中等待 doWork 事件完成。现在我想使用 doWork 中的 writeverbose 向用户报告进度,但由于它是一个不同的线程,我不能这样做。

我已经尝试使用带有 AutoResetEvent 的并发队列来实现此目的,但是否有更好的方法以更简单的方式解决此问题?

最佳答案

如果您正在使用 BackgroundWorker,请订阅它的 ProgressChanged 事件。在 DoWork() 中,您可以使用 ReportProgress() 来触发 ProgressChanged 事件。在 cmdlet 上,ProgressChanged 处理程序可以使用进度信息执行 WriteVerbose 调用。但是,由于您没有调用任何表单,因此您必须手动设置 SynchronizationContext 以使 BackgroundWorker 在您的运行空间线程上执行事件回调。在创建 BackgroundWorker 之前,进行以下调用:

System.Threading.SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());

让 WindowsFormsSynchronizationContext 工作的诀窍是 AFAICT,您必须发送消息。以下是我有限测试的结果:

using System.ComponentModel;
using System.Diagnostics;
using System.Management.Automation;
using System.Threading;
using System.Windows.Forms;

namespace CmdletExp
{

    [Cmdlet("Invoke", "LongRunning")]
    public class InvokeLongRunningCommand : PSCmdlet
    {
        private readonly BackgroundWorker _backgroundWorker;
        private readonly AutoResetEvent _autoResetEvent;

        public InvokeLongRunningCommand()
        {
            SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
            _backgroundWorker = new BackgroundWorker();
            _backgroundWorker.WorkerReportsProgress = true;
            _backgroundWorker.DoWork += _backgroundWorker_DoWork;
            _backgroundWorker.ProgressChanged += _backgroundWorker_ProgressChanged;         
            _autoResetEvent = new AutoResetEvent(false);
        }

        protected override void EndProcessing()
        {
            Debug.WriteLine("EndProcessing ThreadId: " +  Thread.CurrentThread.ManagedThreadId);
            _backgroundWorker.RunWorkerAsync();
            do
            {
                Application.DoEvents();
            } while (!_autoResetEvent.WaitOne(250));
            Application.DoEvents();
            base.EndProcessing();
        }

        void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            Debug.WriteLine("DoWork ThreadId: " + Thread.CurrentThread.ManagedThreadId);
            for (int i = 0; i < 20; i++)
            {
                _backgroundWorker.ReportProgress(i * 5);
                Thread.Sleep(1000);
            }
            _autoResetEvent.Set();
        }

        void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Debug.WriteLine("ProgressChanged ThreadId: " + Thread.CurrentThread.ManagedThreadId + " progress: " + e.ProgressPercentage);
            WriteVerbose("Progress is " + e.ProgressPercentage);
        }
    }
}

像这样调用:

PS> Invoke-LongRunning -Verbose
VERBOSE: Progress is 0
VERBOSE: Progress is 5
VERBOSE: Progress is 10
VERBOSE: Progress is 15
VERBOSE: Progress is 20
VERBOSE: Progress is 25
...

关于c# - Powershell 自定义 cmdlet 在标准方法的实现之外调用 WriteVerbose,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23502680/

相关文章:

c# - 绑定(bind)边界可见性/隐藏在 WPF 中

c# - 如何通过非null属性和ID查找元素?

c# - 如何使用 C# 在 MonoGame 中移动我的 Sprite

multithreading - 为什么内存重新排序在单核/处理器机器上不是问题?

C#用多个逗号替换字符串中的两个特定逗号

c - 双核字数统计速度慢

c# - 从计时器线程更新 WPF UI

powershell - 将简单的PowerShell命令添加到批处理文件

powershell - 无法识别“New-AzureStorageContext”

windows - 无法从 Powershell 中的 Windows 搜索生成电子邮件