使用Caliburn.Micro.ReactiveUI 1.2.2时,不正确的线程访问UI对象

时间:2013-12-19 16:44:07

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

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

搜索互联网并未提供任何信息。我想我错过了一些明显的东西,非常感谢任何帮助。

viewmodel:

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

2 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:0)

您的问题是您正在更改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线程的编组