iam*_*one 3 c# powershell cmdlets multithreading backgroundworker
Msdn 文档对 WriteVerbose 方法说:
“只能从 BeginProcessing、ProcessRecord 和 EndProcessing 方法的实现中调用此方法,并且只能从该线程调用。如果从这些实现外部或另一个线程调用此方法,则会引发 InvalidOperationException 异常。”
但是在我的测试代码中,我能够在三个标准方法之外调用该方法。但是当从不同的线程调用该方法时,我得到了一个异常。
我相信这里的大多数人在实现自定义 cmdlet 时都需要多线程支持。
我正在使用后台工作者来完成简单的多线程。在继续之前,我在 ProcessRecord 函数中等待 doWork 事件完成。现在我想使用来自 doWork 的 writeverbose 向用户报告进度,但由于它是一个不同的线程,我不能这样做。
我已经尝试使用带有 AutoResetEvent 的并发队列来实现这一点,但是有没有更好的方法以更简单的方式解决这个问题?
如果您正在使用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)