2015-12-23 6 views
1

Я пытаюсь узнать, как использовать Runspace для передачи значений в графический интерфейс. Я подкорректировал сценарий, написанный Boe Prox, пытаясь понять, как Dispatcher.Invoke работает с и пространством выполнения попался очень странная проблемойИспользование Dispatcher Invoke с Runspace в Powershell

$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, и текст не добавляется. Однако, если я добавлю Update-Window -Title ("Services on {0}" -f $Env:Computername) прямо над линией Dispatcher.invoke, я все равно получаю ошибку, но текстовое поле содержит прилагаемый текст.

В чем причина этого события? Я пробовал так много способов использовать Dispatcher.Invoke для добавления контента в текстовые поля, но всегда заканчивается ошибкой cannot call a method method on null без каких-либо успехов, но теперь добавление некоторых строк, ссылающихся на пользовательский интерфейс и вызывающих Dispatcher.Invoke, похоже, заставляет его работать.

ответ

1

Есть пара проблем с кодом, которые, вероятно, вызывают ошибочную ошибку. Во-первых, вы используете код из Powershell_ISE или с консоли powershell? Кроме того, вы запускаете сценарий в двух частях, когда вызовы диспетчера выполняются с консоли после того, как окно открыто или как один скрипт, включая вызовы диспетчера? Если вы используете код как отдельный скрипт, проблема в том, что «BeginInvoke» запускает скрипт в пределах своей собственной рабочей области в отдельном потоке. Прежде чем окно будет правильно создано этой нитью, основной поток уже пытается установить значение заголовка и текстового поля.

Если вы хотите разделить код на две части, то есть вызвать все, чтобы начать воспроизведение в одном скрипте, а затем вызвать вызовы диспетчера в главном скрипте, код также будет иметь проблемы, так как вам нужно сделать глобальную хэш-таблицу.

Я изменил исходный код, чтобы он выполнялся в одном скрипте. Обратите внимание на добавление стартового сна для задержки вызовов диспетчера. Результаты показывают идентификаторы потока и время до и после вызова (в тиках), и вы можете ясно видеть, что время после вызова begin перед установкой времени текстового поля.

$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, который обеспечивает BackgroundWorker для WPF/MSForms и обычная консоль Powershell сценариев.

+0

Благодарим вас за это разъяснение. Выполняет ли сценарий через консоль vs Powershell ISE какое-либо значение с точки зрения многопоточности? Будет ли ваш сценарий написан по-другому, если вы хотите развернуть его через консоль? – Bregs

1

Независимо от того, является ли консоль или ISE реальным значением «многопоточности». Разница заключается в области, используемой ISE. Как правило, если вы не используете модификаторы областей или Dot-источник детского скрипта, то родительская область powershell не может получить доступ к дочерним переменным, но дочерняя область наследует любые переменные родителя.

Добавить переменную $ myFirstValue = "Первое значение" перед блоком AddScript и вторую переменную $ mySecondValue = "Второе значение" в блоке AddScript в примере скрипта. Затем в последней строке скрипта добавьте $ Global: uiHash.Window.Dispatcher.Invoke ([action] {$ Global: uiHash.TextBox.AppendText ($ myThirdValue)}, "Normal")

На консоли ISE задано значение $ myThirdValue = "ваше значение". Сделайте то же самое в открытой сессии консоли PowerShell. Запустите сценарий как в ISE, так и в сеансе консоли powershell.

В ISE после запуска скрипта вы сможете получить доступ к значению $ myFirstValue, но не $ mySecondValue, и скрипт отобразит «ваше значение» в текстовом поле.

В сеансе консоли не доступно ни $ myFirstValue, ни $ mySecondValue, но в текстовом поле будет отображаться «ваше значение».

Чтобы объяснить, что происходит, область дочернего скрипта наследует значение $ myThirdValue, поэтому оно отображается во всех случаях. $ mySecondValue явно находится в отдельной рабочей области и поэтому недоступен в любом случае. $ myFirstValue недоступен для сеанса консоли из-за общего правила определения области выше.

Что происходит с ISE, нарушает ли это общее правило? Возможно, по причинам отладки все панели в ISE имеют одинаковую область. Если вы откроете «новую вкладку PowerShell» в меню файла, будет создано отдельное пространство, и эта переменная больше не будет доступна.

Дополнительную помощь можно получить с помощью справки about_scope.

Вынос из всего этого заключается в том, что, если вы пишете скрипт, распространяемый по нескольким файлам, вам может понадобиться использовать модификаторы глобального/сценария, чтобы гарантировать правильность его функционирования при переходе от ISE к сеансу консоли. Я буду использовать ISE для записи своих скриптов, но будет запускать их на одновременном открытом сеансе консоли, так как всегда возможно, что ISE сохраняет значения, которые в противном случае не могут быть доступны для сеанса консоли.

Я упомянул об этом, потому что в сценарии примера вы можете (как я полагаю, BoeProx) удалить последние четыре строки из сценария, а затем запустить сценарий еще раз, а окно открыто, вы можете ввести 4 строки в консоли и измените открытое окно. Выполнение этого способа означает, что вам больше не нужно начинать сон, потому что к моменту ввода первой строки на клавиатуре окно уже открыто (если вы действительно не очень быстро).

Смежные вопросы