C# 从单独线程更新cmdlet进度的最佳方法

C# 从单独线程更新cmdlet进度的最佳方法,c#,.net,multithreading,powershell,cmdlet,C#,.net,Multithreading,Powershell,Cmdlet,我有一个用C#编写的powershell cmdlet(源自PSCmdlet),它将启动一个长时间运行的任务,该任务在运行时应使用WriteProgress()更新其进度。由于powershell不允许单独的线程使用WriteObject或WriteProgress,因此我必须在主线程中创建一个队列,并将要写入管道/进度的任务中的项添加到队列中。while循环将在对象进入并写入pipline/进度条时将其出列 这是可行的,但我想看看是否有更好的实践可以使用C#/VB编写的powershell c

我有一个用C#编写的powershell cmdlet(源自
PSCmdlet
),它将启动一个长时间运行的任务,该任务在运行时应使用
WriteProgress()
更新其进度。由于powershell不允许单独的线程使用
WriteObject
WriteProgress
,因此我必须在主线程中创建一个
队列
,并将要写入管道/进度的任务中的项添加到队列中。while循环将在对象进入并写入pipline/进度条时将其出列


这是可行的,但我想看看是否有更好的实践可以使用C#/VB编写的powershell cmdlet实现多线程。例如,使用WPF,如果需要更新进度条或UI组件,我总是可以使用
UIComponent.Dispatcher.Invoke()
进入UI线程。我是否可以使用任何等效工具来“跨入”powershell线程以更新UI或写入管道?

您可以查看Start-Job cmdlet以及Get-Job、Wait-Job和Receive-Job

Start Job将有效地启动一个新线程并输出一个JobId,您可以使用Receive Job查询该JobId以获得输出。然后,您可以遍历当前运行的所有作业并更新进度条


看一看

这里是一个封装在类中的队列系统示例,这样它更易于使用并模仿Cmdllet.WriteObject的行为。通过这种方式,您可以从单独的线程中调用WriteObject,对象将被封送到powershell线程并写入管道

[Cmdlet("Test", "Adapter")]
public class TestCmdlet : PSCmdlet
{
    protected override void ProcessRecord()
    {
        PowerShellAdapter adapter = new PowerShellAdapter(this, 100);
        Task.Factory.StartNew(() => {
            for (int x = 0; x < 100; x++) {
                adapter.WriteObject(x);
                Thread.Sleep(100);
            }
            adapter.Finished = true;
        });
        adapter.Listen();
    }
}   
[Cmdlet(“测试”、“适配器”)]
公共类TestCmdlet:PSCmdlet
{
受保护的覆盖无效ProcessRecord()
{
PowerShellAdapter=新的PowerShellAdapter(此,100);
Task.Factory.StartNew(()=>{
对于(int x=0;x<100;x++){
adapter.WriteObject(x);
睡眠(100);
}
adapter.Finished=true;
});
adapter.Listen();
}
}   

公共类PowerShellAdapter
{
私有Cmdlet Cmdlet{get;set;}
专用队列队列{get;set;}
私有对象锁令牌{get;set;}
公共bool已完成{get;set;}
公共整数总计{get;set;}
公共整数计数{get;set;}
公共PowerShellAdapter(Cmdlet Cmdlet,int-total)
{
this.Cmdlet=Cmdlet;
this.LockToken=新对象();
this.Queue=新队列();
this.Finished=false;
这个.总计=总计;
}
公共图书馆
{
ProgressRecord progress=新的ProgressRecord(1,“计数到100”,“”);
而(!Finished | | Queue.Count>0)
{
而(Queue.Count>0)
{
progress.PercentComplete=++计数*100/总数;
progress.StatusDescription=计数+“/”+总计;
Cmdlet.WriteObject(Queue.Dequeue());
Cmdlet.WriteProgress(进度);
}
睡眠(100);
}
}
public void WriteObject(object obj)
{
锁(锁令牌)
排队;排队(obj);
}
}

由Despertar提供的答案将起作用,但可以稍加改进

使用Thread.Sleep在循环中轮询,应使用AutoResetEvent替换。这将导致主线程仅在实际有可用数据时“唤醒”,并允许cmdlet以超过100ms的速度完成。Thread.Sleep始终会导致cmdlet至少需要100毫秒,即使它可以运行得更快。如果您有一个简单的cmdlet,这可能不是问题,但是如果您将它插入到一个复杂的管道中,这100ms可能很容易增加,并导致运行非常缓慢。此外,当访问Listen方法内主线程上的队列时,应该使用锁

这个故事的寓意是:如果你做了跨线程同步,那么睡眠不是正确的工具

using System.Threading;
public class PowerShellAdapter
{
    private Cmdlet Cmdlet { get; set; }
    private Queue<object> Queue { get; set; }
    AutoResetEvent sync;
    private object LockToken { get; set; }
    // volatile, since it will be written/read from different threads.
    volatile bool finished;
    public bool Finished
    {
        get { return finished; }
        set
        {
            this.finished = value;
            // allow the main thread to exit the outer loop.
            sync.Set();
        }
    }
    public int Total { get; set; }
    public int Count { get; set; }

    public PowerShellAdapter(Cmdlet cmdlet, int total)
    {
        this.Cmdlet = cmdlet;
        this.LockToken = new object();
        this.Queue = new Queue<object>();
        this.finished = false;
        this.Total = total;
        this.sync = new AutoResetEvent(false);
    }

    public void Listen()
    {
        ProgressRecord progress = new ProgressRecord(1, "Counting to 100", " ");
        while (!Finished)
        {
            while (true) { // loop until we drain the queue
                object item;
                lock (LockToken) {
                    if (Queue.Count == 0)
                        break; // exit while
                    item = Queue.Dequeue();
                }

                progress.PercentComplete = ++Count * 100 / Total;
                progress.StatusDescription = Count + "/" + Total;
                Cmdlet.WriteObject(item);
                Cmdlet.WriteProgress(progress);
            }
            sync.WaitOne();// wait for more data to become available
        }
    }

    public void WriteObject(object obj)
    {
        lock (LockToken)
        {
            Queue.Enqueue(obj);
        }
        sync.Set(); // alert that data is available
    }
}
使用系统线程;
公共类PowerShellAdapter
{
私有Cmdlet Cmdlet{get;set;}
专用队列队列{get;set;}
自动同步;
私有对象锁令牌{get;set;}
//易失性,因为它将从不同的线程写入/读取。
挥发油已完成;
公共厕所完工了
{
获取{return finished;}
设置
{
this.finished=值;
//允许主线程退出外环。
sync.Set();
}
}
公共整数总计{get;set;}
公共整数计数{get;set;}
公共PowerShellAdapter(Cmdlet Cmdlet,int-total)
{
this.Cmdlet=Cmdlet;
this.LockToken=新对象();
this.Queue=新队列();
this.finished=false;
这个.总计=总计;
this.sync=新的自动重置事件(false);
}
公共图书馆
{
ProgressRecord progress=新的ProgressRecord(1,“计数到100”,“”);
当(!完成)
{
while(true){//循环,直到我们耗尽队列
目标项目;
锁(锁令牌){
如果(Queue.Count==0)
break;//退出
item=Queue.Dequeue();
}
progress.PercentComplete=++计数*100/总数;
progress.StatusDescription=计数+“/”+总计;
Cmdlet.WriteObject(项);
Cmdlet.WriteProgress(进度);
}
sync.WaitOne();//等待更多数据可用
}
}
public void WriteObject(object obj)
{
锁(锁令牌)
{
排队;排队(obj);
}
sync.Set();//警告数据可用
}
}
请注意,我还没有实际测试这段代码,但它说明了这一想法。

谢谢您的支持
using System.Threading;
public class PowerShellAdapter
{
    private Cmdlet Cmdlet { get; set; }
    private Queue<object> Queue { get; set; }
    AutoResetEvent sync;
    private object LockToken { get; set; }
    // volatile, since it will be written/read from different threads.
    volatile bool finished;
    public bool Finished
    {
        get { return finished; }
        set
        {
            this.finished = value;
            // allow the main thread to exit the outer loop.
            sync.Set();
        }
    }
    public int Total { get; set; }
    public int Count { get; set; }

    public PowerShellAdapter(Cmdlet cmdlet, int total)
    {
        this.Cmdlet = cmdlet;
        this.LockToken = new object();
        this.Queue = new Queue<object>();
        this.finished = false;
        this.Total = total;
        this.sync = new AutoResetEvent(false);
    }

    public void Listen()
    {
        ProgressRecord progress = new ProgressRecord(1, "Counting to 100", " ");
        while (!Finished)
        {
            while (true) { // loop until we drain the queue
                object item;
                lock (LockToken) {
                    if (Queue.Count == 0)
                        break; // exit while
                    item = Queue.Dequeue();
                }

                progress.PercentComplete = ++Count * 100 / Total;
                progress.StatusDescription = Count + "/" + Total;
                Cmdlet.WriteObject(item);
                Cmdlet.WriteProgress(progress);
            }
            sync.WaitOne();// wait for more data to become available
        }
    }

    public void WriteObject(object obj)
    {
        lock (LockToken)
        {
            Queue.Enqueue(obj);
        }
        sync.Set(); // alert that data is available
    }
}