Question

Msdn documentation says for WriteVerbose method that:

"This method can be called only from within the implementation of the BeginProcessing, ProcessRecord, and EndProcessing methods and only from that thread. If this call is made from outside these implementations or from another thread, an InvalidOperationException exception is thrown."

But during my test code I was able to call the method outside of the three standard methods. But while calling the method from a different thread I get an exception.

I am sure most of the people here needed multithreading support while implementing custom cmdlets.

I am using background worker to accomplish simple multithreading. i wait in my ProcessRecord function for the doWork event to complete before I go ahead. Now I want to report progress to the user using writeverbose from doWork but since its a different thread I cannot do it.

I have tried using a concurrent queue with an AutoResetEvent to achieve this, but can there be a better way to solve this in a more simpler way?

Was it helpful?

Solution

If you're using the BackgroundWorker, subscribe to its ProgressChanged event. Inside DoWork() you can use ReportProgress() to fire ProgressChanged events. On the cmdlet, the ProgressChanged handler can do the WriteVerbose calls with progress info. However, since you aren't invoking any forms you will have to set the SynchronizationContext manually to get the BackgroundWorker to do the event callback on your runspace thread. Before you create the BackgroundWorker, make this call:

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

The trick to getting the WindowsFormsSynchronizationContext to work is that AFAICT, you have to pump messages. The following works from my limited testing:

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

Invoke like so:

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
...

OTHER TIPS

I simply require that all methods I use take a in a Cmdlet, like so;

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

then when I call it from my Cmdlet, i just pass my cmdlet in as this,

bool bSuccess = Program.bpNewLDSInstance(**this**, Computername, InstanceName, InstallType, LDAPPort, SSLPort, dbPath, logPath,
                ImportLDIFFiles, Partition, SourceServer, SourcePort, ServiceAccount, ServicePassword, Administrator, InstIn);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top