3

编辑:示例代码与最初的目标相去甚远,但我实际提出的问题有一个合理的答案,所以我编辑了问题和标题。

我正在使用 WinForms GUI 编写多线程 PowerShell 脚本,并希望从脚本中的其他线程之一更新 GUI。我做这件事一点运气都没有。事实上,我什至不能BeginInvoke去上班。

这是一个非常精简的示例。这是示例代码,而不是生产,所以它不是很漂亮,但它正在复制问题:

$script:SynchronizedUIHash = [Hashtable]::Synchronized(@{});

[reflection.assembly]::LoadWithPartialName( "System.Windows.Forms")
$form= New-Object Windows.Forms.Form
$label = New-Object Windows.Forms.Label
$label.Text = 'original'
$script:SynchronizedUIHash.label = $label

$form.Controls.add($label)
$form.show()
[System.Windows.Forms.Application]::DoEvents()

Read-Host

$label.BeginInvoke(
    [Action[string]] {
        param($Message)
        [Console]::Beep(500,300)
        $l = $SynchronizedUIHash.label
        $l.Text = $Message
        [System.Windows.Forms.Application]::DoEvents()
    },
    'changed'
)

Read-Host

$form.close()

我什至没有听到控制台哔哔声,所以好像BeginInvoke代码根本没有被执行。

[System.Windows.Forms.Application]::DoEvents()如果我在之后投入 a ,Read-Host那么我会看到变化,所以这似乎是一个时间问题,但我不想进入自旋循环或类似的愚蠢 - 我想开火并忘记BeginInvoke并得到结果视情况可见。我看到了其他答案,例如PowerShell: Job Event Action with Form not executed but that need to be in a check, sleep, check loop。

必须有一种更简单的方法,我错过了......它是什么?

什么是正确的解决方案?

编辑:基思的评论让我意识到我错过了从生产到剥离示例代码的关键点——启动工作确实是多进程的,但示例代码不是,所以示例不能反映现实生活,我将不得不重新考虑整个事情。话虽如此 - 示例代码不使用作业,它也不能按我的预期工作,所以我想了解原因,即使在这种特定情况下这对我没有帮助。

4

2 回答 2

2

您不能真正让控制台和 Windows 在同一个线程上运行。我建议您创建另一个线程来托管窗口,如下所示(这是一个 C# 控制台应用程序,但应该很容易翻译):

class Program
{
    static void Main(string[] args)
    {
        Form form = new Form();
        Label label = new Label();
        label.Text = "original";
        form.Controls.Add(label);

        // run this form on its own thread, so it can work properly
        Thread t = new Thread((state) => Application.Run((Form)state));
        t.Start(form);

        Console.ReadLine();

        // note you *could* do label.Text = "other" here, but BeginInvoke is always recommended
        label.BeginInvoke((Action)delegate() { label.Text = "other"; });

        Console.ReadLine();
        Application.Exit();
    }
}

编辑:我花了一些时间来制作 Powershell 版本,但它就在这里。诀窍是:您不能从 Powershell 环境中创建标准的 .NET 线程。时期。一旦你尝试这样做,你就会使 Powershell 崩溃。导致崩溃的根本错误是:

System.Management.Automation.PSInvalidOperationException:没有可用于在此线程中运行脚本的运行空间。您可以在 System.Management.Automation.Runspaces.Runspace 类型的 DefaultRunspace 属性中提供一个

而且,您不能真正尝试从另一个线程设置此 DefaultRunspace 变量,因为它被标记为 threadstatic(每个线程一个副本)!现在,如果你 google 多线程 Powershell,你会发现很多关于 Powershell Jobs 的文章。但是这些允许多任务处理,但它不是真正的多线程,它更重量级。

所以...原来解决方案是真正使用 Powershell 功能,即:Powershell 运行空间。这里是:

[reflection.assembly]::LoadWithPartialName( "System.Windows.Forms")
$form = New-Object System.Windows.Forms.Form
$label = New-Object System.Windows.Forms.Label
$label.Text = 'original'
$form.Controls.add($label)

$ps = [powershell]::create()
$ps.AddScript(
     {
     [System.Windows.Forms.Application]::Run($form)
     })
$ps.Runspace.SessionStateProxy.SetVariable("form", $form)
$ps.BeginInvoke()

Read-Host

$label.BeginInvoke(
    [Action[string]] {
        param($Message)
        $label.Text = $Message
    },
    'changed'
)

Read-Host

[System.Windows.Forms.Application]::Exit()
$ps.Dispose()
于 2013-01-18T17:00:05.497 回答
0

Invoke()-ing 跨线程工作。您的 PowerShell 脚本将获得它自己的进程。在那个级别上,您正在查看进程间通信解决方案,例如远程处理或消息队列,而不是 Invoke() 之类的线程间通信工具。

在 Powershell 中,其中最简单的可能是您自己的小“消息队列”,其中 powershell 将某些内容写入 winforms 应用程序正在监视的文本文件中,并且知道数据出现时要做什么。

于 2013-01-18T15:10:17.333 回答