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()));
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 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.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Thread HeartRateThread = new Thread(startThread);
HeartRateThread.Name = "Class1";
HeartRateThread.Start();
}
private void startThread(object obj)
{
new Class1();
}
}
public class Class1
{
public Class1()
{
DispatcherTimer timer1 = new DispatcherTimer();
timer1.Interval = new TimeSpan(0,0,0,1);
timer1.Tick += timer1_tick;
timer1.Start();
}
private void timer1_tick(object sender, EventArgs e)
{
Debug.WriteLine("timer called");
}
}
I am trying to enable this timer_tick function fromanother thread as it is obvious in the code section of maInWindow. However, the Class1 constructor is called but timertick functin is not enabled. However if i do this on the main thread, everything works fine. Any reason for this.And how can I get it working?
DispatcherTimer can only work run on a UI thread. However, in your case you are creating a DispatcherTimer on a background thread. DispatcherTimer, internally tries to get Dispatcher.CurrentDispatcher, in your case it gets dispatcher for the background thread, not for the main UI thread.
Do you really need DispatcherTimer? If you are not going to manipulate any UI elements in the timer1_tick method, then you are better off to go with a different timer, like System.Timers.Timer.
Refer to this to read more about available Timers in .net.
Maybe you can try something like this:
private void timer1_tick(object sender, EventArgs e)
{
Application.Current.Dispatcher.BeginInvoke(new Action(() => Debug.WriteLine("timer called")));
}
Without having tested it, I guess you have to pass the MainWindow's Dispatcher to the DispatcherTimer on construction. Otherwise it will create its own:
private void startThread(object obj)
{
new Class1(Dispatcher);
}
...
public Class1(Dispatcher dispatcher)
{
DispatcherTimer timer1 =
new DispatcherTimer(DispatcherPriority.Background, dispatcher);
timer1.Interval = new TimeSpan(0,0,0,1);
timer1.Tick += timer1_tick;
timer1.Start();
}
you can use Dispatcher for call startThread method.
object objParameter = "parametervalue";
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(
() => startThread(objParameter)));
In my application's Business Logic layer I have the following classes:
public class EocMonitor : DeviceMonitor {
public BackgroundWorker BackendWorker { get; set; }
public BackgroundWorker EocWorker { get; set; }
public EocMonitor() {
BackendWorker = new BackgroundWorker {
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
BackendWorker.DoWork += BackendWorker_DoWork;
EocWorker = new BackgroundWorker {
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
EocWorker.DoWork += EocWorker_DoWork;
}
private void BackendWorker_DoWork( object sender, DoWorkEventArgs e ) {
// Does some lengthy operation
}
void EocWorker_DoWork( object sender, DoWorkEventArgs e ) {
// Does some lengthy operation
}
public void GetDiagnostics() {
BackendWorker.RunWorkerAsync( new DiagnosticsInfo() );
EocWorker.RunWorkerAsync( new DiagnosticsInfo() );
}
}
public class DiagnosticsInfo {
public int DataTypeCount { get; set; }
public int DataTypesProcessed { get; set; }
}
The BackgroundWorkers are used to query information over the wire from 2 other processes running in my application. The responses can take a while to come back. Plus the data can take a while to come back.
I have a WPF UserControl in my application's main window called Dashboard. The Dashboard has a DataGrid on it that displays the results of the lengthy operations. Because they are lengthy, it also has a Button on it called Refresh that starts the process off. And, because it can take a long time to run, there's a UserControl I wrote called a ProgressControl on the form. This consists of a Cancel Button, a ProgressBar, and a TextBlock where messages can be displayed. When the user clicks on the Cancel Button, the refresh stops.
Here's some code from Dashboard:
public partial class Dashboard : UserControl {
public Dashboard() {
InitializeComponent();
}
private Dashboard_Loaded( object sender, RoutedEventArgs e ) {
if ( !setupProgress && EocMonitor != null ) {
EocMonitor.BackendWorker.ProgressChanged += BackendWorker_ProgressChanged;
EocMonitor.BAckendWorker.RunWorkerCompleted += BackendWorker_RunWorkerCompleted;
EocMonitor.EocWorker.ProgressChkanged += EocWorker_ProgresChanged;
EocMonitor.EocWorker.RunWorkerCompleted += EocWorker_RunWorkerCompleted;
}
}
private void BackendWorker_ProgressChanged( object sender, ProgressChangedEventArgs e ) {
DiagnosticsInfo info = e.UserState as DiagnosticsInfo;
// Other processing to notify the user of the progress
}
private void BackendWorker_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) {
// Processing to do when the back-ground worker is finished
}
private void DiagnosticsProgressCtrl_Click( object sender, RoutedEventArgs e ) {
EocMonitor.BackendWorker.CancelAsync();
EocMonitor. EocWorker.CancelAsync();
DiagnosticsProgressCtrl.Visibility = Visibility.Collapsed;
e.Handled = true;
}
void EocWorker_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) {
// Processing to do when the back-ground worker is finished
}
private void RefreshButton_Click( object sender, RoutedEventArgs e ) {
DiagnosticsProgressCtrl.Maximum = DiagnosticsProgressCtrl.Minimum = DiagnosticsProgressCtrl.Value = 0.0;
DiagnosticsProgressCtrl.Visibility = Visibility.Visible;
backendDataTypeCount = eocDataTypeCount = 0;
backendWorkerCompleted = eocWorkerCompleted = false;
EocMonitor.GetDiagnostics();
e.Handled = true;
}
}
The problem is that I have placed breakpoints in the DoWork methods and watched them run to completion, yet the RunWorkerCompleted methods are not being called. No errors are occurring or being thrown. This thing is the EocMonitor class and the Dashboard class are in two different DLLs. Does that make a difference? As far as I know it shouldn't, but I don't understand why the completed event handlers aren't getting called. Should I instantiate the BackgroundWorkers in the front-end application?
Tony
The event is raised, but you don't see it because you didn't subscribe to the RunWorkerCompleted event...
BackendWorker.RunWorkerCompleted += BackendWorker_RunWorkerCompleted;
EocWorker.RunWorkerCompleted += EocWorker_RunWorkerCompleted;
Well, after I posted the above, I went back and changed things a bit. I now instantiate the BackgroundWorker objects in the Dashboard control and pass them to the EocMonitor's GetDiagnostics method. The properties in EocMonitor that hold these objects have private setters, so the only way to use them is to create them & pass them to that method. The code in the Dashboard_Loaded is now moved in the RefreshButton_Click method and runs after the objects are instantiated, before they're passed to GetDiagnostics.
This all works now! I see the Progress_Changed methods and the RunWorkerCompleted methods run.
It just hit me why it's probably not working. The EocMonitor object is created on a non UI thread during my program's initalization phase. Since it's calling methods in a UI object, the methods probably can't be called. An Invalid operation exception of some sort is probably being thrown, but there's no place to catch it.
So let that be a lesson: The BackgroundWorker has to be instantiated in code on the UI thread.
I'm getting this Exception
System.InvalidOperationException was
unhandled by user code Message="The
calling thread cannot access this
object because a different thread owns
it."
whenever I run the following code
public partial class MainScreen : Window
{
Timer trm;
public MainScreen()
{
InitializeComponent();
trm = new Timer(1000);
trm.AutoReset = true;
trm.Start();
trm.Elapsed += new ElapsedEventHandler(trm_Elapsed);
}
void trm_Elapsed(object sender, ElapsedEventArgs e)
{
lblTime.Content = System.DateTime.Now;
}
}
guys any solution... I badly wann come out of it :(
Use DispatcherTimer instead:
public partial class MainScreen : Window{
DispatcherTimer tmr;
public MainScreen() {
InitializeComponent();
tmr = new DispatcherTimer();
tmr.Tick += new EventHandler(tmr_Tick);
tmr.Start();
}
void tmr_Tick(object sender, EventArgs e) {
lblTime.Content = System.DateTime.Now;
}
}
Any time you modify Windows controls you must do so on the UI thread (the one that created the controls).
See this question for lots of details.
To be short, you should use Dispatcher.Invoke method to update UI elements.