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/