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

iam*_*one 3 c# powershell cmdlets multithreading backgroundworker

Msdn 文档对 WriteVerbose 方法说:

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

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

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

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

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

Kei*_*ill 5

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

System.Threading.SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
Run Code Online (Sandbox Code Playgroud)

使 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

像这样调用:

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
...
Run Code Online (Sandbox Code Playgroud)