如何正确使用ReactiveUI :: WhenAnyValue与备用Windows线程?

时间:2017-05-29 06:58:44

标签: c# .net wpf multithreading reactiveui

我已经构建了一个小型日志记录GUI来帮助调试主应用程序。它需要在它自己的线程上运行,因此当主UI变得无响应时它不会被阻止。日志窗口的部分目的是找出主窗口无响应的原因。

通过此方法启动新窗口。

    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);

现在我确信上面的调用在新的STA线程上运行WhenAnyValue。我可以在调试器中检查这个。

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回调中发生错误。它发生在reactiveui代码内部,因为它试图读取Text的{​​{1}}属性。添加

TextBox

无法解决问题。

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

作为santiy检查我是否删除来自代码的WhenAnyValue调用,然后我不会收到此错误。

似乎ReactiveUI对它应该运行的同步上下文感到困惑。但也许我做错了什么。这个问题有解决方案/解决方法吗?

修改

要重现错误,您需要插入对

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

在应用程序启动早期的某些时候。复制错误的项目就在这里。

https://github.com/bradphelan/RxUIBug1375

2 个答案:

答案 0 :(得分: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(""));
    }
}

答案 1 :(得分: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);