我有一个用C#编写的powershell cmdlet (从PSCmdlet
派生),它将启动一个长时间运行的任务,该任务应在WriteProgress()
运行时更新其进度。由于powershell不允许单独的线程使用WriteObject
或WriteProgress
我必须在主线程中创建Queue<object>
并且我从我想要的任务中将项添加到队列中写入管道/进度。 while循环会在对象进入并将其写入pipline / progress bar时将其取出。
这是有效的,但我想看看是否有更好的多线程实践,使用用C#/ VB编写的powershell cmdlet。例如,对于WPF,如果我需要更新进度条或UI组件,我总是可以使用UIComponent.Dispatcher.Invoke()
进入UI线程。有没有什么可以用来“踩到”powershell线程来更新UI或写入管道?
答案 0 :(得分:3)
以下是封装在类中的队列系统的示例,因此它更易于使用并模仿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();
}
}
public class PowerShellAdapter
{
private Cmdlet Cmdlet { get; set; }
private Queue<object> Queue { get; set; }
private object LockToken { get; set; }
public bool Finished { get; 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;
}
public void Listen()
{
ProgressRecord progress = new ProgressRecord(1, "Counting to 100", " ");
while (!Finished || Queue.Count > 0)
{
while (Queue.Count > 0)
{
progress.PercentComplete = ++Count*100 / Total;
progress.StatusDescription = Count + "/" + Total;
Cmdlet.WriteObject(Queue.Dequeue());
Cmdlet.WriteProgress(progress);
}
Thread.Sleep(100);
}
}
public void WriteObject(object obj)
{
lock (LockToken)
Queue.Enqueue(obj);
}
}
答案 1 :(得分:1)
您可以查看Start-Job cmdlet以及Get-Job,Wait-Job和Receive-Job。
Start-Job将有效地启动一个新线程并输出一个JobId,您可以使用Receive-Job查询它以获得输出。然后,您可以遍历所有当前正在运行的作业并更新进度条。
答案 2 :(得分:1)
Despertar提供的答案将有效,但可以略微改进。
使用ThreadReleEvent替换Thread.Sleep循环中的轮询。这将导致主线程仅唤醒&#34;当实际存在可用数据时,可以允许cmdlet以超过100ms的速度完成。 Thread.Sleep将始终使cmdlet至少需要100毫秒,即使它可以运行必须更快。如果你有一个简单的cmdlet,这可能不是问题,但是如果你将它插入到一个复杂的管道中,这个100ms就可以很容易地繁殖并导致事情慢慢地运行非常。此外,在Listen方法内的主线程上访问Queue时应该进行锁定。
故事的寓意:如果你做跨线程同步Thread.Sleep不是正确的工具。
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
}
}
注意,我还没有真正测试过这段代码,但它说明了这个想法。