ReactiveUI The calling thread cannot access this object because a different thread owns it
Asked Answered
Z

2

2

If I use a binding in code behind, getting an error after click at change IsBusy

"The calling thread cannot access this object because a different thread owns it"

xaml:

<Button x:Name="AsyncCommand"
                    Height="20"
                    Content="PushAsync"/>
<ProgressBar x:Name="IsBusy"
              Height="20"/>

cs:

this.Bind(ViewModel, x => x.IsBusy, x => x.IsBusy.IsIndeterminate);
this.BindCommand(ViewModel, x => x.AsyncCommand, x => x.AsyncCommand);

viewmodel:

public class TestViewModel : ReactiveObject
    {
        public TestViewModel()
        {
            AsyncCommand = new ReactiveAsyncCommand();
            AsyncCommand
                .RegisterAsyncFunction(x => 
                 { IsBusy = true; Thread.Sleep(3000); return "Ok"; })
                .Subscribe(x => { IsBusy = false; });
        }

        private bool isBusy;

        public bool IsBusy
        {
            get { return isBusy; }
            set { this.RaiseAndSetIfChanged(x => x.IsBusy, ref isBusy, value); }
        }
        public ReactiveAsyncCommand AsyncCommand { get; protected set; }
    }

But if I make a bind in xaml all works, like this:

cs:

DataContext = new TestViewModel();

xaml:

<Button x:Name="AsyncCommand"
                    Height="20"
                    Content="PushAsync"
                    Command="{Binding AsyncCommand}"/>
<ProgressBar x:Name="IsBusy"
              Height="20"
              IsIndeterminate="{Binding IsBusy}"/>

Why is this happening?

Zacheryzack answered 16/5, 2013 at 17:59 Comment(0)
W
2

Try this:

public TestViewModel()
{
    AsyncCommand = new ReactiveAsyncCommand();
    AsyncCommand.Subscribe(_ => IsBusy = true);

    AsyncCommand
        .RegisterAsyncFunction(x => 
         { Thread.Sleep(3000); return "Ok"; })
        .Subscribe(x => { IsBusy = false; });
}

Or even better:

ObservableAsPropertyHelper<bool> _IsBusy;
public bool IsBusy {
    get { return _IsBusy.Value; }
}

public TestViewModel()
{
    AsyncCommand = new ReactiveAsyncCommand();
    AsyncCommand
        .RegisterAsyncFunction(x => 
         { Thread.Sleep(3000); return "Ok"; })
        .Subscribe(x => { /* do a thing */ });

    AsyncCommand.ItemsInFlight
        .Select(x => x > 0 ? true : false)
        .ToProperty(this, x => x.IsBusy);
}
Warily answered 16/5, 2013 at 20:40 Comment(2)
How do I invoke a synchronous method on UI thread from a WhenAnyValue?Ashes
Found my answer here. Thank you!Ashes
B
0

I assume that your ViewModel property is implemented similiar to this:

public TestViewModel ViewModel
{
    get { return (TestViewModel)DataContext; }
    set { DataContext = value; }
}

In that case, when you click button, lambda function from your RegisterAsyncFunction is called on non-UI thread. In IsBusy = false instruction ReactiveUI invokes ViewModel property which tries to get DataContext on non-UI thread, which causes InvalidOperationException.

If you bind your ViewModel to View in Xaml, then ViewModel property isn't called.

To fix this code you should use Dispatcher.Invoke to call IsBusy = false:

AsyncCommand
    .RegisterAsyncFunction(x => 
    {
        Application.Current.Dispatcher.Invoke(() =>IsBusy = true); 
        Thread.Sleep(3000); 
        return "Ok"; 
    })'
Baumgardner answered 16/5, 2013 at 19:56 Comment(2)
This is incorrect advice for ReactiveUI (though you've got the core bug correct)Warily
You don't want your VM to know about Application.Ashes

© 2022 - 2024 — McMap. All rights reserved.