I have a ReactiveCommand that refreshes data and is bound to a Button in XAML. The functionality works fine, but I also want to execute the command on a timer.
I have the following code - SetupAutoRefresh is called from the ctor in my VM, but when the Observable fires, I get an exception with the message: "The calling thread cannot access this object because a different thread owns it."
VM:
private void SetupAutoRefresh() {
Observable.Timer(TimeSpan.FromSeconds(5))
.Select(_ => Unit.Default)
.ObserveOn(RxApp.MainThreadScheduler)
.InvokeCommand(RefreshData);
RefreshData = ReactiveCommand.CreateFromTask(Refresh);
}
private async Task Refresh()
{
var updatedData = await _repository.GetAll();
Data.Merge(updatedData);
}
private ReactiveCommand<Unit, Unit> _refreshData;
public ReactiveCommand<Unit, Unit> RefreshData
{
get { return _refreshData; }
set { this.RaiseAndSetIfChanged(ref _refreshData, value); }
}
private IReactiveList<Model> _data;
public IReactiveList<Model> Data
{
get { return _data; }
set { this.RaiseAndSetIfChanged(ref _data, value); }
}
XAML:
<Button Grid.Column="2"
Command="{Binding RefreshData}"
Style="{StaticResource ToolbarButtonTheme}"
Content="{StaticResource RefreshToolbarIcon}"
ToolTip="Refresh Search"/>
Debug output provides this stacktrace:
at System.Windows.Threading.Dispatcher.VerifyAccess()
at System.Windows.DependencyObject.GetValue(DependencyProperty dp)
at System.Windows.Controls.Primitives.ButtonBase.get_Command()
at System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute()
at System.Windows.Controls.Primitives.ButtonBase.OnCanExecuteChanged(Object >sender, EventArgs e)
at
System.Windows.Input.CanExecuteChangedEventManager.HandlerSink.OnCanExecuteChanged(Object sender, EventArgs e)
at ReactiveUI.ReactiveCommand.OnCanExecuteChanged() in C:\projects\reactiveui\src\ReactiveUI\ReactiveCommand.cs:line 628
I've tried many different variations of attempting to schedule this on the RxApp.MainThreadScheduler but without any joy - ObserveOn, SubscribeOn, setting the output scheduler... none of which I had much hope for anyway.
Feel like I'm missing something obvious here, but have been banging my head against a brick wall for the whole afternoon. Surely this scenario is possible in RxUI?
The Refresh method runs on a background thread; you can't modify databound properties within that method.
Try this:
private void SetupAutoRefresh() {
Observable.Timer(TimeSpan.FromSeconds(5))
.Select(_ => Unit.Default)
// remove ObserveOn here; the Command will run on the background
.InvokeCommand(RefreshData);
RefreshData = ReactiveCommand.CreateFromTask(Refresh);
// RefreshData.Subscribe is guaranteed to run on the UI thread
RefreshData.Subscribe(listOfModels => Data.Merge(listOfModels))
}
private async Task Refresh()
{
// all this method does is deliver a list of models
return await _repository.GetAll();
}
// return IEnumerable<Model> from the command
public ReactiveCommand<Unit, IEnumerable<Model>> RefreshData
Now, your ReactiveCommand simply fetches the new data, and returns it to you on the UI thread within Subscribe :)
Figured out the issue - looks like the Observable needed to be created on the UI thread. I missed it from the original post, but the SetupAutoRefresh method had been called from another async method, which had switched context during a prior await.
Related
I have a WPF application with a view containing a data grid and a view model with an observable collection that is initialized by calling an asynchronous method in the constructor. But the data grid remains empty upon running the code.
The view model class looks like this.
internal class MainWindowViewModel : INotifyPropertyChanged
{
private readonly IBookingRecordService service;
public event PropertyChangedEventHandler? PropertyChanged;
private ObservableCollection<BookingRecord> bookingRecords = new();
public ObservableCollection<BookingRecord> BookingRecords
{
get => bookingRecords;
set
{
bookingRecords = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BookingRecords)));
}
}
public MainWindowViewModel()
{
service = new BookingRecordService();
Task.Run(() => LoadBookingRecords());
}
private async Task LoadBookingRecords()
{
BookingRecords = new ObservableCollection<BookingRecord>(await service.Get());
}
}
I all LoadBookingRecords() in the constructor, so that the data starts loading on initialization of the view model already but I do it asynchronously, so it does not block the UI thread and makes the application unresponsive.
I have tried waiting for the completion of the task in the constructor via
Task.Run(() => LoadBookingRecords()).Wait();
to check that this has something to do with the asynchronous function call. And indeed, if I wait for the method to finish in the constructor, the data grid displays correctly. But I don't want to wait for the task to finish on the UI thread because it blocks the UI.
I have read that you must raise the PropertyChanged event on the UI thread to trigger a UI update and I suppose that is the problem here. I have also read that one can use
Application.Current.Dispatcher.BeginInvoke()
to schedule a delegate to run on the UI thread as soon as possible, so I tried the following.
private async Task LoadBookingRecords()
{
await Application.Current.Dispatcher.BeginInvoke(new Action(async () =>
{
BookingRecords = new ObservableCollection<BookingRecord>(await service.Get());
}));
}
But this leaves the DataGrid empty as well.
"'asynchronous ... in constructor" is something you must avoid.
Async calls must be awaited, which can not be done in a constructor. Declare an awaitable Initialize method instead
public Task Initialize()
{
return LoadBookingRecords();
}
and call it in an async Loaded event handler in your MainWindow:
private static async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
await viewModel.Initialize();
}
Alternatively, create a factory method like
public static async Task<MainWindowViewModel> Create()
{
var viewModel = new MainWindowViewModel();
await viewModel.LoadBookingRecords();
return viewModel;
}
and call that in the Loaded handler:
private static async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
DataContext = await MainWindowViewModel.Create();
}
Building on Clemens' answer, I tried something a little different in order to avoid touching the MainWindow code-behind.
I removed the call on LoadBookingRecords in the constructor and instead created a delegate command as a property that holds this method.
internal class MainWindowViewModel : INotifyPropertyChanged
{
private readonly IBookingRecordService service;
private ObservableCollection<BookingRecord> bookingRecords = new();
public ICommand LoadBookingRecordsCommand { get; set; }
public ObservableCollection<BookingRecord> BookingRecords
{
get => bookingRecords;
set
{
bookingRecords = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BookingRecords)));
}
}
public MainWindowViewModel()
{
service = new BookingRecordService();
LoadBookingRecordsCommand = new DelegateCommand(async _ => await LoadBookingRecords());
}
private async Task LoadBookingRecords()
{
BookingRecords = new ObservableCollection<BookingRecord>(await service.Get());
}
}
I then added the NuGet package Microsoft.Xaml.Behaviors.Wpf to the project and added the following namespace to the MainWindow XAML.
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
Finally, I bound the delegate command to the MainWindow's Loaded event.
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadBookingRecordsCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Now the data grid displays correctly after being loaded.
I have a method defined in the Model that would execute a long running script where I want to capture the output message when the script is in progress and output to the View via the ViewModel. I understand in order to get realtime update of the output message I should run the Model method in a backgroundworker and raise its ReportProgress event when it has output message to report in order to run the UI update and the script on two separate threads. The problem I have is the backgroundworker object is defined in the ViewModel, so using it to call the Model method is straight forward, but how do I raise the ReportProgress event from the Model method? The only way I can think of is passing in the backgroundworker as input parameter into the method but I feel uneasy about this. Can anyone tell me if this is the right approach in implementing the MVVM framework?
Here are my code stripped to the most bare bone. In my View xaml I have a TextBox bind to the Logger property and DeployCommand command in my ViewModel:
<TextBox Grid.Row="1 " Name="txtOutput" MinHeight="40"
Text="{Binding Logger}"
IsReadOnly="True" Margin="10,10" VerticalScrollBarVisibility="Auto"
IsEnabled="True" MaxLines="2000" TextWrapping="WrapWithOverflow"/>
<Button x:Name="BtnDeploy"
Command="{Binding DeployCommand}"
Content="Deploy"
Height="23"
Margin="5,2"
HorizontalAlignment="Right"
Width="125"
FontFamily="Kalinga"
AutomationProperties.AutomationId="DeployButton"/>
In my ViewModel, the DeployCommand command will trigger the method OnDeploy which in turn will call the Deploy method in Model using the backgroundworker object:
private string logger = string.Empty;
public string Logger
{
get { return logger; }
set
{
logger = value;
RaisePropertyChanged("Logger");
}
}
public ICommand DeployCommand { get; private set; }
public MainWindowViewModel()
{
_worker = new BackgroundWorker()
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_worker.DoWork += worker_DoWork;
// _worker.RunWorkerCompleted += worker_RunWorkerCompleted;
_worker.ProgressChanged += worker_ProgressChanged;
DeployController = new DeploymentModel();
this.DeployCommand = new DelegateCommand<object>(this.OnDeploy);
}
private void OnDeploy(object obj)
{
Logger += #"Offline Deployment Started" + System.Environment.NewLine;
if (!_worker.IsBusy)
{
_worker.RunWorkerAsync(DeployController);
}
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = (BackgroundWorker)sender;
var deployModel = (DeploymentModel)e.Argument;
deployModel.Deploy(script);
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Logger += e.UserState.ToString();
}
Finally in the Model:
public bool Deploy(string ScriptFile)
{
bool Success = true;
string strCmdText = string.Format(#"/c ""{0}""", ScriptFile);
try
{
var startInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = kitFolder,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
FileName = "cmd.exe",
CreateNoWindow = true,
Arguments = strCmdText,
};
// Launch shell command to run powersheel script
using (Process myProcess = Process.Start(startInfo))
{
// capturing script output message
myProcess.OutputDataReceived += (s, e) =>
{
LogMessage("ExecuteDeploymentKit: " + e.Data);
};
myProcess.ErrorDataReceived += (s, e) =>
{
Success = false;
LogMessage("ExecuteDeploymentKit: ! > " + e.Data);
};
myProcess.BeginErrorReadLine();
myProcess.BeginOutputReadLine();
System.Threading.Thread.Sleep(5000);
myProcess.WaitForExit();
}
}
catch (Exception ex)
{
LogMessage("ExecuteDeploymentKit: " + ex.Message);
return false;
}
if (Success)
{
LogMessage("ExecuteDeploymentKit: Offline Deployment Kit executed successfully");
}
else
{
LogMessage("ExecuteDeploymentKit: Offline Deployment Kit failed");
}
return Success;
}
I have added workder_ProgressChanged to handle the ProgressChanged event of the backgroundworker in order to update the View in the UI thread but without the backgroundworker object in my Model, I can't raise the ProgressChanged event from the method Deploy()
Thanks
The standard way would be for your VM to implement the IProgress interface and pass your M the VM cast as an IProgress object. You shouldn't pass it the VM since that could be a reference nightmare.
But really, the background worker should be implemented in the VM, not the M. And you shouldn't use BackgroundWorker anymore and move onto the new async methods.
If I understand your question right, you might be breaking core principles of MVVM by letting the Model drive your viewmodel and view. Without really having much to go off of, I would suspect that the best approach to this would be to actually create a "service".
Keep your model dumb and let it only contain data. Think POCO. Then, utilize a service that implements a background worker. Have your View Model run the service. The View model can call the service and provide that service a reference to your instantiated model. This way, you aren't heavily coupling your model to your view model.
I am having problem with the application freezing. Let me explain my scenario, I have a service which does an async call to a database to get a list of items, It is run by a task. Inside this task I have a try catch block, so it looks like this
public Task<List<T>> ComboListAsync(int? id = null, EnumDTO dto = EnumDTO.Default)
{
return Task.Run(() =>
{
using (var context = new ContextService())
{
try
{
return GetComboList(id, dto, context);
}
catch (Exception e)
{
Handler.DatabaseConnectionException();
throw;
}
}
});
}
Then it throws an exception as GetComboList its just this (for the moment)
protected virtual List<T> GetComboList(int? id, EnumDTO dto, ContextService context)
{
throw new NotImplementedException();
}
So the call catches the exception and goes inside here
public void Show(string message)
{
Message = message;
Application.Current.Dispatcher.Invoke(() =>
{
dialogView = new DialogView() {DataContext = this, Owner = Application.Current.MainWindow};
dialogView.ShowDialog();
});
}
Now the Dispatcher freezes the app, I tried to change it to use begin invoke, it does the same. Without the dispatcher I get an error message that the calling thread is not a STA. I simply want to display my message in a dialog window, that there was a problem connecting to a database. Can anyone help?
I looked online and there is many threads about dispatcher, but none actually show a solution that will fix my issue.
Thank you
EDIT
Code which calls the ComboListAsync
protected override void RetrieveRelatedActiveLists()
{
MyCollection = service.ComboListAsync().Result;
}
Its a deadlock because of the calling code is using the .Result.
Using service.ComboListAsync().Result makes the UI thread await for this method to return, when you call Application.Current.Dispatcher.Invoke from within it you are sending a message to the UI thread that is awaiting the return of method itself.
You must await the method service.ComboListAsync() like this:
protected override async void RetrieveRelatedActiveLists()
{
MyCollection = await service.ComboListAsync();
}
I am having a problem with getting data from db and showing in UI asynchronously.
I am using MVVM light, when I click the button, action is triggered in ViewModel:
private void SearchQuery(string query)
{
_redisModel.GetFriendsListAsync(query);
}
At some point GetFriendsListCompleted is called by background thread notifing viewmodel that job is done.
At this point I need to update ListBox ItemSource. But when I try to update is I get
“The calling thread cannot access this object because a different thread owns it”
I have tried Dispatcher.CurrentDispatcher.Invoke(),App.Current.Dispatcher.Invoke() and different magic, but it still doesn’t work.
I tried to give UI dispatcher to ViewModel and then call it from there - didn't work.
private string filterText = string.Empty;
public string FilterText
{
get { return filterText; }
set
{
filterText = value;
this.RaisePropertyChanged(() => this.FilterText);
this.FriendsList.View.Refresh(); // Here where exception is happening.
}
}
I tried to change this line to
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() =>this.FriendsList.View.Refresh())); - still the same.
I am using Telerik ListBox to display items. FriendList is CollectionViewSource(http://www.telerik.com/help/wpf/radlistbox-overview.html). It works when I use Telerik example from WPF Control Examples. Problems start to occur when I use my async methods.
Type of view is System.ComponentModel.ICollectionView it is used for Filtering and Grouping.
I have also tried to just assign ObservableCollection to Items property of the ListBox and it doesn't work either.
A bit more details on how _redisModel.GetFriendsListAsync works:
In the end(after all chain of calls) it ends up here:
public GetAsyncResult(Func<T> workToBeDone, Action<IAsyncResult> cbMethod, Object state)
{
_cbMethod = cbMethod;
_state = state;
QueueWorkOnThreadPool(workToBeDone);
}
ThreadPool.QueueUserWorkItem(state =>
{
try
{
_result = workToBeDone();
}
catch (Exception ex)
{
_exception = ex;
}
finally
{
UpdateStatusToComplete(); //1 and 2
NotifyCallbackWhenAvailable(); //3 callback invocation
}
});
In viewmodel I have method:
private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
{
if (!e.HasError)
{
var curr = e.Results;
if (curr != null)
{
this.FriendsList= new CollectionViewSource();
this.FriendsList.Source = list;
this.FriendsList.Filter += this.FriendFilter;
FilterText = "";
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() => this.FriendsList.View.Refresh()));
}
}
Can anybody please help me with this ?
Thank you
You are creating CollectionViewSource in one thread and refreshing that in another thread (dispatcher thread). Update your GetFriendsListCompleted to
private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
{
if (!e.HasError)
{
var curr = e.Results;
if (curr != null)
{
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() => {
this.FriendsList= new CollectionViewSource();
this.FriendsList.Source = list;
this.FriendsList.Filter += this.FriendFilter;
FilterText = "";
this.FriendsList.View.Refresh();
}));
}
}
}
You haven't shown any of the code that's actually running on the background thread on completion but I'm guessing that in it you're creating a collection object that you're then trying to assign to your CollectionView. When the CV tries to update (on the UI thread) from your Refresh call it would then try to use the collection that's owned by the other thread.
If you include the relevant code it would be easier to say for sure.
I have the following code running in a WPF app:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
object obj = new object();
Collection.Add(obj);
Collection.CollectionChanged += new NotifyCollectionChangedEventHandler(delegate(object sender2, NotifyCollectionChangedEventArgs e2)
{
if (Collection.Count == 0)
App.Current.MainWindow.Close();
});
Task.Factory.StartNew(() =>
{
//Do long running process
Collection.Remove(obj); //this errors out
});
}
private ObservableCollection<object> Collection = new ObservableCollection<object>();
}
I get the error System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it.
I was under the impression that Task.Factory.StartNew queued up an async task, so the thread should be the same, no?
Task.Factory.StartNew executes your action in the default TaskScheduler, so it will run in the ThreadPool.
ObservableCollection is not thread-safe. It means that your CollectionChanged handler, which performs operations on UI controls ( App.Current.MainWindow.Close() ) is not going to be executed in the UI thread because the collection modification is being done in your Task's action, causing the error you are seeing.
If you only need to interact with the UI in your handler, you can use the dispatcher:
Collection.CollectionChanged += new NotifyCollectionChangedEventHandler(delegate(object sender2, NotifyCollectionChangedEventArgs e2)
{
if (Collection.Count == 0)
this.Dispatcher.BeginInvoke((Action)(()=> App.Current.MainWindow.Close()));
});
If you need to bind to it, consider using a thread-safe implementation. See this.
Just to add to Arthur's answer, in my real application (not the sample code above) I needed to do this from an MvvmLight view model. To access the dispatcher from a ViewModel:
Inside App, add the following:
static App()
{
DispatcherHelper.Initialize();
}
And then instead of calling this.Dispatcher, because a ViewModel has no reference to the Dispatcher, the following will work:
DispatcherHelper.UIDispatcher.BeginInvoke((Action)(() => App.Current.MainWindow.Close()));