簡體   English   中英

在Powershell中將Dispatcher Invoke與運行空間一起使用

[英]Using Dispatcher Invoke with a Runspace in Powershell

我正在嘗試學習如何使用運行空間將值傳遞給GUI。 我一直在調整Boe Prox編寫的腳本,試圖了解Dispatcher.Invoke如何與運行空間一起工作,並遇到了一個非常奇怪的問題

 $uiHash = [hashtable]::Synchronized(@{}) $newRunspace =[runspacefactory]::CreateRunspace() $newRunspace.ApartmentState = "STA" $newRunspace.ThreadOptions = "ReuseThread" $newRunspace.Open() $newRunspace.SessionStateProxy.SetVariable("uiHash",$uiHash) $psCmd = [PowerShell]::Create().AddScript({ $uiHash.Error = $Error [xml]$xaml = @" <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="Window" Title="Initial Window" WindowStartupLocation = "CenterScreen" Width = "650" Height = "800" ShowInTaskbar = "True"> <TextBox x:Name = "textbox" Height = "400" Width = "600"/> </Window> "@ $reader=(New-Object System.Xml.XmlNodeReader $xaml) $uiHash.Window=[Windows.Markup.XamlReader]::Load( $reader ) $uiHash.TextBox = $uiHash.window.FindName("textbox") $uiHash.Window.ShowDialog() | Out-Null }) $psCmd.Runspace = $newRunspace $handle = $psCmd.BeginInvoke() #----------------------------------------- #Using the Dispatcher to send data from another thread to UI thread Update-Window -Title ("Services on {0}" -f $Env:Computername) $uiHash.Window.Dispatcher.invoke("Normal",[action]{$uiHash.TextBox.AppendText('test')}) 

如果我使用的腳本的最后一行沒有Update-Window -Title ("Services on {0}" -f $Env:Computername)我得到的是you cannot call a method on a null-valued expression. InvokeMethodOnNull錯誤,並且未附加文本。 但是,如果我在Dispatcher.invoke行的正上方添加Update-Window -Title ("Services on {0}" -f $Env:Computername) ,我仍然會收到錯誤消息,但文本框包含附加的文本。

發生這種情況的原因是什么? 我已經嘗試了很多方法來使用Dispatcher.Invoke將內容添加到文本框,但總是以cannot call a method method on null錯誤cannot call a method method on null而沒有成功,但是現在添加了一些引用UI並調用Dispatcher.Invoke的行似乎使它工作。

您的代碼有幾個問題,很可能會導致錯誤的出現。 首先,您是從Powershell_ISE還是從Powershell控制台運行代碼? 另外,您是將腳本分為兩部分來運行,還是在打開窗口后從控制台進行調度程序調用,還是將其作為包含調度程序調用的單個腳本運行? 如果您將代碼作為單個腳本運行,則問題是“ BeginInvoke”在單獨的線程中在其自己的運行空間中運行腳本。 在此線程正確創建窗口之前,主線程已經在嘗試設置title和文本框的值。

如果要將代碼分為兩部分,即在一個腳本中調用所有內容以進行begininvoke,然后在主腳本中進行調度程序調用,則在使哈希表變為全局時,代碼也會遇到問題。

我已經修改了您的原始代碼,使其可以在單個腳本中運行。 請注意開始睡眠的添加,以延遲調度程序調用。 結果顯示線程ID以及調用之前和之后的時間(以滴答為單位),您可以清楚地看到開始調用之后的時間是在設置文本框時間之前的時間。

$Global:uiHash = [hashtable]::Synchronized(@{})
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"          
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("uiHash",$Global:uiHash)



$psCmd = [PowerShell]::Create().AddScript({   
    $Global:uiHash.Error = $Error
    Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase
    $xaml = @"
    <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Name="Window" Title="Initial Window" WindowStartupLocation = "CenterScreen"
        Width = "650" Height = "800" ShowInTaskbar = "True">
        <Grid>
        <TextBox x:Name = "textbox" Height = "400" Width = "600" TextWrapping="Wrap"/>
        </Grid>
    </Window>
"@
   # $reader=(New-Object System.Xml.XmlNodeReader $xaml)
    $Global:uiHash.Window=[Windows.Markup.XamlReader]::Parse($xaml )
    $Global:uiHash.TextBox = $Global:uiHash.window.FindName("textbox")
    $Global:uiHash.TextBox.Text = "Window Creation Thread Id: $([System.Threading.Thread]::CurrentThread.ManagedThreadId.ToString()) Time: $([System.Diagnostics.Stopwatch]::GetTimestamp()) `r`n" 
    $Global:uiHash.Window.ShowDialog() | out-null
})
$psCmd.Runspace = $newRunspace
$time1 = " Time before beginInvoke: $([System.Diagnostics.Stopwatch]::GetTimestamp()) `r`n"
$handle = $psCmd.BeginInvoke()

#-----------------------------------------
$time2 = " Time after beginInvoke: $([System.Diagnostics.Stopwatch]::GetTimestamp()) `r`n"
#Using the Dispatcher to send data from another thread to UI thread
Start-Sleep -Milliseconds 100
#Update-Window -Title ("Services on {0}" -f $Env:Computername)
$threadId = " Dispatcher Call Thread Id: $([System.Threading.Thread]::CurrentThread.ManagedThreadId.ToString())  Time: $([System.Diagnostics.Stopwatch]::GetTimestamp())`r`n "

$Global:uiHash.Window.Dispatcher.Invoke([action]{$Global:uiHash.TextBox.AppendText($time1)},"Normal")
$Global:uiHash.Window.Dispatcher.Invoke([action]{$Global:uiHash.TextBox.AppendText($time2)},"Normal")
$Global:uiHash.Window.Dispatcher.Invoke([action]{$Global:uiHash.TextBox.AppendText($threadId)},"Normal")
$Global:uiHash.Window.Dispatcher.Invoke([action]{$Global:uiHash.Window.Title = "$($env:ComputerName)"},"Normal")

您可能還希望下載WPFRunspace ,這是一個Powershell模塊,為WPF / MSForms和普通控制台Powershell腳本提供了背景工作。

控制台還是ISE與實際的“多線程”沒有任何實質性的區別。 區別在於ISE使用的范圍。 通常,除非使用子腳本的范圍修飾符或Dot采購,否則父級Powershell范圍無法訪問子變量,但子級范圍將繼承父級的任何變量。

添加一個變量

$myFirstValue = "The first value"

在AddScript塊和第二個變量之前

$mySecondValue = "The second value" 

在示例腳本的AddScript塊中。 然后在腳本的最后一行添加

$Global:uiHash.Window.Dispatcher.Invoke(
    [action]{$Global:uiHash.TextBox.AppendText($myThirdValue)},"Normal")

在ISE控制台中,設置

$myThirdValue = "your value"

在打開的Powershell控制台會話中執行相同的操作。 在ISE和powershell控制台會話中運行腳本。

腳本運行后,在ISE中,您將能夠訪問$myFirstValue的值,但不能訪問$mySecondValue ,腳本將在文本框中顯示“您的值”。

在控制台會話中, $myFirstValue$mySecondValue均不可訪問,但“您的值”將顯示在文本框中。

為了解釋發生了什么,子腳本作用域繼承了$myThirdValue的值,因此在所有情況下都將其顯示。 $mySecondValue顯然位於單獨的運行空間中,因此無論如何都無法訪問。 由於上面的常規作用域規則,控制台會話無法訪問$myFirstValue

ISE發生了什么,是否違反了一般規則? 可能出於調試原因,ISE中的所有窗格共享同一作用域。 如果從文件菜單中打開“新的powershell選項卡”,則會創建一個單獨的運行空間,並且該變量將不再可用。

您可以使用get-help about_scope獲得更多幫助。

所有這一切的收獲是,如果您要編寫分布在多個文件中的腳本,則可能需要使用global / script修飾符,以確保從ISE遷移到控制台會話時腳本可以正常運行。 我將使用ISE編寫腳本,但是將在並發打開的控制台會話中運行它們,因為ISE始終可能保留控制台會話無法使用的值。

我已經提到了這一點,因為在示例腳本中,您可以(按照我認為BoeProx的意圖)從腳本中刪除最后四行,然后再次運行該腳本,當窗口打開時,您可以在控制台中輸入4行並更改打開窗口。 這樣操作意味着您不再需要開始睡眠,因為到鍵盤上輸入第一行時,窗口已經打開(除非您真的非常快)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM