【问题标题】:Incorrect thread accesses UI object when using Caliburn.Micro.ReactiveUI 1.2.2使用 Caliburn.Micro.ReactiveUI 1.2.2 时不正确的线程访问 UI 对象
【发布时间】:2013-12-19 16:44:07
【问题描述】:

我正在尝试使用 Caliburn.Micro.ReactiveUI 编写一个非常简单的示例,此示例结合了 Basic Configuration, Actions and Conventions sample from the Caliburn.Micro DocumentationReactiveUI.Sample.Commands sample。我遇到的问题是,当调用任何命令时,对于DisplayCommandStartAsyncCommand,都会抛出InvalidOperationException,说明调用线程无法访问该对象,因为不同的线程拥有它。下面我包含了 View 和 ViewModel 的代码,整个示例位于 GitHub 代码现在包含下面 Paul 建议的修复。

在互联网上搜索没有得到任何信息。我想我遗漏了一些明显的东西,非常感谢任何帮助。

视图模型:

public class ShellViewModel : ReactivePropertyChangedBase, IShell
{
    private string personName;

    public ShellViewModel(IMessageBoxService messageBoxService)
    {
        DisplayCommand = new ReactiveCommand(this.WhenAny(x => x.PersonName, x => !string.IsNullOrEmpty(x.Value)));
        DisplayCommand.Subscribe(_ => messageBoxService.ShowMessageBox("You clicked on DisplayCommand: Name is " + PersonName));

        var localProgress = new Subject<int>();
        localProgress.ToProperty(this, x => x.Progress, out progress);

        StartAsyncCommand = new ReactiveCommand();
        StartAsyncCommand.RegisterAsyncAction(_ =>
        {
            var currentProgress = 0;
            localProgress.OnNext(currentProgress);
            while (currentProgress <= 100)
            {
                localProgress.OnNext(currentProgress += 10);
                Thread.Sleep(100);
            }
        });
    }

    public IReactiveCommand DisplayCommand { get; protected set; }

    public string PersonName
    {
        get { return personName; }
        set
        {
            this.RaiseAndSetIfChanged(ref personName, value);
        }
    }

    private ObservableAsPropertyHelper<int> progress;
    public int Progress
    {
        get { return progress.Value; }
    }

    public IReactiveCommand StartAsyncCommand { get; protected set; }
}

观点:

<Window x:Class="CaliburnMicroReactiveUI.Sample.Commands.Views.ShellView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel>
        <Expander
                IsExpanded="True"
                Header="Simple Command">
            <StackPanel>
                <TextBox
                        Text="{Binding PersonName, UpdateSourceTrigger=PropertyChanged}" />
                <Button
                        Command="{Binding DisplayCommand}"
                        Content="Display" />
            </StackPanel>
        </Expander>
        <Expander
                IsExpanded="True"
                Header="Async Command">
            <StackPanel>
                <Button Command="{Binding StartAsyncCommand}" Content="Start" />
                <ProgressBar Value="{Binding Progress, Mode=OneWay}" Height="20"/>
            </StackPanel>
        </Expander>
    </StackPanel>
</Window>

显示按钮生成以下堆栈跟踪:

System.Windows.Threading.Dispatcher.VerifyAccess()
System.Windows.DependencyObject.GetValue(DependencyProperty dp)
System.Windows.Controls.Primitives.ButtonBase.get_Command()
System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute()
System.Windows.Controls.Primitives.ButtonBase.OnCanExecuteChanged(Object sender, EventArgs e)
System.Windows.Input.CanExecuteChangedEventManager.HandlerSink.OnCanExecuteChanged(Object sender, EventArgs e)
ReactiveUI.ReactiveCommand.raiseCanExecuteChanged(EventArgs e)
ReactiveUI.ReactiveCommand.<.ctor>b__5()
System.Reactive.Concurrency.Scheduler.Invoke(IScheduler scheduler, Action action)
System.Reactive.Concurrency.DefaultScheduler.<>c__DisplayClass1`1.<Schedule>b__0(Object _)
System.Reactive.Concurrency.ConcurrencyAbstractionLayerImpl.<>c__DisplayClass1.<QueueUserWorkItem>b__0(Object _)
System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
System.Threading.ThreadPoolWorkQueue.Dispatch()
System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

开始按钮创建此堆栈跟踪:

System.Windows.Threading.Dispatcher.VerifyAccess()
System.Windows.DependencyObject.GetValue(DependencyProperty dp)
System.Windows.Controls.Primitives.ButtonBase.get_Command()
System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute()
System.Windows.Controls.Primitives.ButtonBase.OnCanExecuteChanged(Object sender, EventArgs e)
System.Windows.Input.CanExecuteChangedEventManager.HandlerSink.OnCanExecuteChanged(Object sender, EventArgs e)
ReactiveUI.ReactiveCommand.raiseCanExecuteChanged(EventArgs e)
ReactiveUI.ReactiveCommand.<.ctor>b__5()
System.Reactive.Concurrency.Scheduler.Invoke(IScheduler scheduler, Action action)
System.Reactive.Concurrency.DefaultScheduler.<>c__DisplayClass1`1.<Schedule>b__0(Object _)
System.Reactive.Concurrency.ConcurrencyAbstractionLayerImpl.<>c__DisplayClass1.<QueueUserWorkItem>b__0(Object _)
System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
System.Threading.ThreadPoolWorkQueue.Dispatch()
System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

【问题讨论】:

    标签: multithreading caliburn.micro reactiveui caliburn.micro.reactiveui


    【解决方案1】:

    我查看了您的 GitHub 存储库,看起来您刚刚添加了包 Caliburn.Micro.ReactiveUI。这意味着您只有 ReactiveUI 核心,而您缺少特定于平台的扩展。如果你添加包reactiveui-xaml,一切都会更好(不过你仍然需要初始化那个messageBoxService!)。

    【讨论】:

    • 感谢您解决了所有问题。 ReactiveUI-Xaml 应该是 Caliburn.Micro.ReactiveUI 的依赖项吗?
    • 是的,几乎可以肯定
    • ReactiveUI-Xaml 不是Caliburn.Micro.ReactiveUI 的技术依赖项,但添加依赖项可能有意义...说实话,我不确定ReactiveUI 的每个包到底是什么为了。 @PaulBetts,您愿意解释一下这些差异吗?谢谢!
    • 由于向后兼容的原因,许多 RxUI 包都留在那里(即现在只是元包),所以人们不会被破坏。你真正应该关心的是 ReactiveUI-Core 和 ReactiveUI-Platforms
    • 谢谢,这就是我的猜测......所以,剩下的问题是:在不需要它的包中引用 ReactiveUI-Platforms 是否有意义,因为应用程序需要它?奇怪的是,我收到了来自 Calibrun.Micro 团队的完全相反的要求,即只在包中引用 Caliburn.Micro.Core (github.com/dchaib/Caliburn.Micro.ReactiveUI/issues/14)...
    【解决方案2】:

    您的问题是您正在更改 RegisterAsyncAction 中的 Progress,这明确保证它不会在 UI 线程上运行。这是一个更好的方法:

    ObservableAsPropertyHelper<int> _progress;
    public int Progress 
    {
        get { return _progress.Value; }
    }
    
    public ShellViewModel(IMessageBoxService messageBoxService)
    {
        DisplayCommand = new ReactiveCommand(this.WhenAny(x => x.PersonName, x => !string.IsNullOrEmpty(x.Value)));
        DisplayCommand.Subscribe(_ => messageBoxService.ShowMessageBox("You clicked on DisplayCommand: Name is " + PersonName));
    
        var progress = new Subject<int>();
        progress.ToProperty(this, x => x.Progress, out _progress);
    
        StartAsyncCommand = new ReactiveCommand();
        StartAsyncCommand.RegisterAsyncAction(_ =>
        {
            var currentProgress = 0;
            progress.OnNext(currentProgress);
            while (currentProgress <= 100)
            {
                progress.OnNext(currentProgress += 10);
                Thread.Sleep(100);
            }
        });
    }
    

    在这种情况下,您使用 ToProperty 的保证,它会为您处理 UI 线程的封送处理

    【讨论】:

    • 感谢您的解释,显然我的问题不够明确,但我得到了 DisplayCommand 和 StartAsyncCommand 的异常。我已经更新了问题以使其更清楚,您对 DisplayCommand 有什么建议吗?
    • 我无法编译您的代码:System.Reactive.Subjects.Subject&lt;int&gt; 不包含ToProperty 的定义,并且最佳扩展方法重载ReactiveUI.OAPHCreationHelperMixin.ToProperty&lt;TObj,TRet&gt;(System.IObservable&lt;TRet&gt;, TObj, System.Linq.Expressions.Expression&lt;System.Func&lt;TObj,TRet&gt;&gt;, out ReactiveUI.ObservableAsPropertyHelper&lt;TRet&gt;, TRet, System.Reactive.Concurrency.IScheduler) 有一些无效参数。
    • 糟糕,将 'ref' 更改为 'out'
    • 完成了,问题仍然存在,我已经用堆栈跟踪更新了问题,并将项目放在GitHub
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-12
    • 2017-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多