我已经构建了一个小型日志记录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。我可以在调试器中检查这个。
和
如果我让程序继续,我会
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
使用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);
在应用程序启动早期的某些时候。复制错误的项目就在这里。
答案 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);