2017-05-29 741 views
0

我已經構建了一個小型日誌記錄GUI來幫助調試主應用程序。它需要在它自己的線程上運行,以便在主UI無響應時不會被阻止。記錄窗口的一部分目的是找出主窗口爲什麼沒有響應。如何正確使用ReactiveUI :: WhenAnyValue替代Windows線程?

新窗口通過此方法啓動。

public static Task<T> CreateAndShowStaWindow<T>(Func<T> factory) where T:Window 
    { 

     var windowResult = new TaskCompletionSource<T>(); 

     var newWindowThread = new Thread(() => 
     { 
      var window = factory(); 
      window.Show(); 
      windowResult.SetResult(window); 
      window.Events().Closed.Subscribe(_ => window.Dispatcher.InvokeShutdown()); 
      System.Windows.Threading.Dispatcher.Run(); 

     }); 
     newWindowThread.SetApartmentState(ApartmentState.STA); 
     newWindowThread.IsBackground = true; 
     newWindowThread.Start(); 
     newWindowThread.Name = "STA WPF"; 

     return windowResult.Task; 
    } 

窗口裏面我有一個文本框

<TextBox x:Name="FilterText"/> 

我希望監視使用ReactiveUI WhenAnyValue變化

this 
     .WhenAnyValue(p => p.FilterText.Text) 
     .Subscribe(Console.WriteLine); 

現在我確信上述呼叫到WhenAnyValue是在新的STA線程上運行。我可以在調試器中檢查它。

enter image description here

enter image description here

如果我讓程序繼續,我得到

InvalidOperationException異常:因爲不同的線程擁有它調用線程不能訪問該對象。在「MAIN」線程,而不是「STA WPF」線程,並在在堆棧

ReactiveUI.dll!ReactiveUI.Reflection.TryGetValueForPropertyChain<object>(out object changeValue, object current, System.Collections.Generic.IEnumerable<System.Linq.Expressions.Expression> expressionChain) Line 129 
ReactiveUI.dll!ReactiveUI.ReactiveNotifyPropertyChangedMixin.observedChangeFor(System.Linq.Expressions.Expression expression, ReactiveUI.IObservedChange<object, object> sourceChange) Line 134 
ReactiveUI.dll!ReactiveUI.ReactiveNotifyPropertyChangedMixin.nestedObservedChanges(System.Linq.Expressions.Expression expression, ReactiveUI.IObservedChange<object, object> sourceChange, bool beforeChange) Line 142 
ReactiveUI.dll!ReactiveUI.ReactiveNotifyPropertyChangedMixin.SubscribeToExpressionChain.AnonymousMethod__1(ReactiveUI.IObservedChange<object,> object> y) Line 104 

enter image description here

使用WPF這點發生

這個例外是不允許跨線程訪問WPF依賴項屬性。問題是爲什麼ReactiveUI交叉線程訪問

請注意,錯誤是而不是發生在Console.WriteLine作爲傳遞給訂閱的回調。它發生在「內部」代碼中,因爲它試圖讀取TextBoxText屬性。添加

this 
     .WhenAnyValue(p => p.FilterText.Text) 
     .ObserveOn(DispatcherScheduler.Current) 
     .Subscribe(Console.WriteLine); 

不能解決問題。同樣沒有

this 
    .WhenAnyValue(p => p.FilterText.Text) 
    .SubscribeOn(DispatcherScheduler.Current) 
    .Subscribe(Console.WriteLine); 

作爲santiy檢查,如果我刪除從代碼中調用WhenAnyValue然後我沒有得到這個錯誤。

似乎ReactiveUI正在與它應該操作的同步上下文混淆。但也許我做錯了什麼。有沒有解決方案/解決這個問題的方法?

編輯

重現您將需要調用的應用程序啓動初期插入到

Locator.CurrentMutable.InitializeReactiveUI(); 

一些點的誤差。複製錯誤的項目就在這裏。

https://github.com/bradphelan/RxUIBug1375

+0

downvoter會照顧評論嗎? – bradgonesurfing

+0

你的factory()方法是做什麼的? – mm8

+0

@ mm8創建一個窗口。例如'CreateAndShowStaWindow(()=> new LoggerWindow())'。您可以返回任何窗口的子類。 – bradgonesurfing

回答

0

[更新]

我沒有正確地理解這個問題。你大概可以使用SubscribeOn,但是將它默認爲調用上下文而不是主線程會更好。

[老]

你斷點設置定義WhenAny呼叫的線路。當定義了WhenAny訂閱時,您可以確定它在您的STA線程上。但訂閱可以(並且通常會)在新線程上觀察,因爲這就是Rx的工作方式;異步。

你可以通過在你傳遞給Subscribe()的方法上加一個斷點來確認,你會看到它是從另一個線程調用的。

您可以使用.ObserveOn()方法來定義在哪個線程上觀察訂閱。

RxUI有一個IScheduler設置來引用你的主線程:RxApp.MainThreadScheduler,你可以直接把它傳給.ObserveOn()

this 
    .WhenAnyValue(p => p.FilterText.Text) 
    .ObserveOn(RxApp.MainThreadScheduler) 
    .Subscribe(Console.WriteLine); 
+0

是的,我知道我在哪裏設置斷點。重點在於ReactiveUI應該注意同步上下文,並且不要爲鏈的任何部分切換線程。該窗口是在線程「STA WPF」上產生的。初始化代碼在「STA WPF」上運行。沒有很好的理由爲什麼應該在線程「MAIN」上分派任何東西。 – bradgonesurfing

+0

而你寫的代碼仍然會失敗。 (出於兩個原因)(1)當框架嘗試讀取錯誤線程上的依賴屬性_FilterText.Text_時發生錯誤。 ObserveOn不會改變這一點。它只會在您通過的調度程序中向下遊傳遞結果值。 (2)如果你讀到這個問題,你會看到我沒有使用RxApp.MainThreadScheduler,我已經在一個新的線程上啓動了一個新的調度器。 – bradgonesurfing

+0

對不起,我不熟悉WPF,所以我猜測STA線程是主線程,但看起來我錯了。 :) – Qonstrukt

1

但也許我做錯了

可能是因爲我的作品,如果我創建這樣的窗口:

Thread newWindowThread = new Thread(new ThreadStart(() => 
{ 
    SynchronizationContext.SetSynchronizationContext(
     new DispatcherSynchronizationContext(
      Dispatcher.CurrentDispatcher)); 

    RxWindow tempWindow = new RxWindow(); 
    tempWindow.Closed += (ss, ee) => Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background); 
    tempWindow.Show(); 

    Dispatcher.Run(); 
})); 
newWindowThread.SetApartmentState(ApartmentState.STA); 
newWindowThread.IsBackground = true; 
newWindowThread.Start(); 
newWindowThread.Name = "STA WPF"; 

RxWindow.xaml.cs :

public partial class RxWindow : Window 
{ 
    public RxWindow() 
    { 
     InitializeComponent(); 

     this.WhenAnyValue(p => p.FilterText.Text) 
      .Subscribe(_ => MessageBox.Show("")); 
    } 
} 
+0

我已將我的代碼提取到新項目中,並且無法重現該錯誤。我甚至將我的項目的一大塊複製到一個新項目中,但仍然沒有錯誤。不過謝謝你嘗試複製。我目前對真正的問題可能是什麼感到困惑。 – bradgonesurfing

+0

我找到了重現方法。訣竅是在運行之前在代碼的某處插入對'Locator.CurrentMutable.InitializeReactiveUI();的調用。然後它失敗了。複製項目是https://github.com/bradphelan/RxUIBug1375和問題正在跟蹤https://github.com/reactiveui/ReactiveUI – bradgonesurfing