3

編集:サンプルコードは元の目標からはほど遠いですが、私が実際に提起した質問に対する正当な答えがあるので、質問とタイトルを編集しました。

WinForms GUIを使用してマルチスレッドPowerShellスクリプトを作成していますが、スクリプト内の他のスレッドの1つから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()その後にスローインするとRead-Host、変化が見られるのでタイミングの問題のようですが、スピンループなどのばかげたものにはなりたくありません-ファイアアンドフォーゲットしBeginInvokeて結果を出したい必要に応じて表示されます。PowerShellに対するもののような他の回答が表示されます:フォームが実行されていないジョブイベントアクションですが、チェック、スリープ、チェックループにある必要があります。

私が行方不明になっているより簡単な方法がなければなりません...それは何ですか?

ここでの正しい解決策は何ですか?

編集:キースのコメントは、私が本番からストリップされたサンプルコードに移行する重要なポイントを逃したことに気づきました-ジョブの起動は確かにマルチプロセスですが、サンプルコードはそうではないので、サンプルは実際の生活を反映していません、そして私は全体を考え直す必要があります。とはいえ、サンプルコードはジョブを使用せず、期待どおりに機能しないため、この特定のケースでは役に立たない場合でも、その理由を理解したいと思います。

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 (スレッドごとに 1 つのコピー) とマークされているためです。ここで、マルチスレッド Powershell をググると、Powershell ジョブに関する記事がたくさん見つかります。しかし、これらはマルチタスクを可能にしますが、実際にはマルチスレッドではなく、より重いものです。

つまり...解決策は実際には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 に答える