Question

I'm trying to write a very simple sample using Caliburn.Micro.ReactiveUI this sample combines the Basic Configuration, Actions and Conventions sample from the Caliburn.Micro Documentation with the ReactiveUI.Sample.Commands sample. The problem I have is that when any command gets called, so for both the DisplayCommand and the StartAsyncCommand, an InvalidOperationException is thrown, stating that the calling thread cannot access this object because a different thread owns it. Below I've included the code of both the View and the ViewModel, the entire sample is on GitHub the code now contains the fix suggested by Paul below.

Searching the internet did not yield any information. I guess I'm missing something obvious, any help is very much appreciated.

The 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; }
}

The view:

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

The Display button generates the following stacktrace:

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

The Start button creates this stacktrace:

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()
Was it helpful?

Solution

I have had a look at your GitHub repository and it looks like you just added the package Caliburn.Micro.ReactiveUI. It means that you just have ReactiveUI core and your are missing the platform specific extensions. If you add the package reactiveui-xaml, everything works better (you will still need to initialize that messageBoxService though!).

OTHER TIPS

Your problem is that you're changing Progress in your RegisterAsyncAction, which explicitly guarantees that it won't be run on the UI thread. Here's a better way you could do this:

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

In this case, you're using the guarantee of ToProperty that it handles marshaling to the UI thread for you

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top