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

时间:2014-05-06 18:51:30

标签: c# multithreading powershell backgroundworker cmdlets

Msdn文档说WriteVerbose方法:

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

但在我的测试代码中,我能够在三种标准方法之外调用该方法。但是当从另一个线程调用该方法时,我得到一个例外。

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

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

我尝试使用带有AutoResetEvent的并发队列来实现这一点,但是有更好的方法可以更简单的方式解决这个问题吗?

2 个答案:

答案 0 :(得分:4)

如果您使用的是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
...

答案 1 :(得分:-1)

我只是要求我使用的所有方法都使用Cmdlet,就像这样;

public static bool bpNewLDSInstance(**Cmdlet ni**, string Computername, string InstanceName, installType InstallType, int LDAPPort, int SSLPort, string dbPath, string logPath,
          string ImportLDIFFiles, string Partition, string SourceServer, int SourcePort, string ServiceAccount, SecureString ServicePassword, string Administrator, ADLDSInstance InstIn)
{
    bool bInstanceValidated = false;
    ni.WriteVerbose("1");
}

然后当我从我的Cmdlet调用它时,我只是将cmdlet传递给了,

bool bSuccess = Program.bpNewLDSInstance(**this**, Computername, InstanceName, InstallType, LDAPPort, SSLPort, dbPath, logPath,
                ImportLDIFFiles, Partition, SourceServer, SourcePort, ServiceAccount, ServicePassword, Administrator, InstIn);