View not updating when property is changed by event from another thread - c#

I am trying to run something like a background thread and notify a ViewModel of the status of this background thread by raising an event. The ViewModel in turn raises the OnPropertyChanged event. Unfortunately the corresponding view will not update.
The part of the background thread where I notify the ViewModel can be seen here:
private void RunThread() {
while(true) {
suspendEvent.WaitOne(Timeout.Infinite);
if (shutDownEvent.WaitOne(0)) {
break;
}
if (pauseEvent.WaitOne(0)) {
pauseEvent.Reset();
}
Notify("Cleaning started!");
pauseEvent.WaitOne(TimeSpan.FromSeconds(5));
Clean();
Notify("Cleaning finished!");
pauseEvent.WaitOne(TimeSpan.FromSeconds(5));
}
}
private void Notify( String status ) {
NotifyOfCleanerStatusHandler handler = NotifyOfcleanerStatus;
if (handler != null) {
handler(status);
}
}
The part of the ViewModel where I receive the event can be seen here:
public void SetCleanerStatus( String status ) {
CleanerStatus = status;
}
And finally, the property that I bind my view to is seen here:
public String CleanerStatus {
get {
return cleanerStatus;
}
set {
if (value != null) {
cleanerStatus = value;
OnPropertyChanged("CleanerStatus");
}
}
}
The intriguing thing is: The View will show one of the above statuses, either "Cleaning started!" or "Cleaning finished!". One would think that they should alternate in an interval of 5 seconds. This is not the case.
If I remove the first waithandle call (pauseEvent.WaitOne(TimeSpan.FromSeconds(5));) the message in the view is "Cleaning finished" and stays that way. If i keep the line, the message is "Cleaning started!" and stays that way. Debugging shows that the line OnPropertyChanged(..) is reached.
What am I doing wrong?

Simplify your RunThread method:
private ManualResetEvent suspendEvent = new ManualResetEvent(false);
private bool shutdown;
private void RunThread()
{
while (!shutdown)
{
suspendEvent.WaitOne();
if (shutdown)
{
break;
}
Notify("Cleaning started!");
Thread.Sleep(TimeSpan.FromSeconds(5));
Notify("Cleaning finished!");
Thread.Sleep(TimeSpan.FromSeconds(5));
}
}
When updating the UI it might also be necessary to invoke the PropertyChanged handler in the UI thread:
public string CleanerStatus
{
get { return cleanerStatus; }
set
{
cleanerStatus = value;
App.Current.Dispatcher.BeginInvoke(
new Action(() => OnPropertyChanged("CleanerStatus")));
}
}

Related

Update MVVM binding from event fired by asynchronous task

I have a task that runs a continuous TCP read operation, when that read operation reads a JSON encoded message from the remote server it will fire an event to the Command.cs class where it will handle the raw JSON and convert it into a Response class, do the necessary checks and then fires an 'Finished' event where ultimately the client subscribes to where they can receive a Response class that can be added to a Observable Collection.
I've tried to implement a RaisePropertyChanged that calls the Invoke function on the Dispatcher that should update the ObservableCollection properly.
TCPRequest.cs
private static CancellationTokenSource cancellation;
private static event OnDataReceived datareceivedevent;
public static event OnDataReceived DataReceivedEvent
{
add
{
if(datareceivedevent == null)
{
datareceivedevent += value;
}
}
remove
{
datareceivedevent -= value;
}
}
private static async void ReadOperation(object t)
{
var token = (CancellationToken)t;
var stream = tcpClient.GetStream();
var byteBuffer = new byte[tcpClient.ReceiveBufferSize];
while (!token.IsCancellationRequested)
{
int lRead = 0;
if (stream.DataAvailable)
{
lRead = await stream.ReadAsync(byteBuffer, 0, byteBuffer.Length);
}
if (lRead > 0)
{
var response = ASCIIEncoding.ASCII.GetString(byteBuffer, 0, lRead);
datareceivedevent(response);
}
}
}
public static void StartReading()
{
Task.Factory.StartNew(ReadOperation, cancellation.Token, cancellation.Token);
}
The above code is the task that runs until a cancel is requested. When the data is available it will convert to string and will fire the event (showed above).
Command.cs
public event OnDataReceivedDeserialized OnDataReceivedDeserialized;
public bool Execute()
{
this.JSONFormat = ToJson();
TCPRequest.DataReceivedEvent += TCPRequest_DataReceivedEvent;
if (!JSONFormat.Equals(string.Empty))
{
return TCPRequest.SendToServer(this);
}
else
{
return false;
}
}
The execute function is within a Command class that handles everything needed to send JSON to the server. This Command class subscribes to the TCPRequest datareceivedevent event.
if (TCPRequest.IsConnected)
{
Command cmd = new Command();
cmd.RequestCommand = new Request(RequestType.info);
cmd.OnDataReceivedDeserialized += Cmd_OnDataReceivedDeserialized;
cmd.Execute();
}
public void Cmd_OnDataReceivedDeserialized(RequestResponse response)
{
LoPyList.Add(new LoPy() { name = "Test", id = "00" });
}
And then above it will create the command that subscribes to the command event where the value of the server response will go.
GraphViewModel.cs
private ObservableCollection<LoPy> lopyList;
public ObservableCollection<LoPy> LoPyList
{
get { return lopyList; }
set {
lopyList = value;
RaisePropertyChangedEvent("LoPyList"); }
}
And lastly, above is the LoPyList that is binded to the interface combobox.
GraphView.xaml
<ComboBox Grid.Row="1" ItemsSource="{Binding Path=LoPyList}"
DisplayMemberPath="name"/>
The thing I need is that the function inside of the ViewModel updates the LoPyList and that I can view it in the UI.
When your event fires, what you are doing is calling the "Add" method on the ObservableCollection:
LoPyList.Add(new LoPy() { name = "Test", id = "00" });
The problem here is that your RaisePropertyChangedEvent only occurs when you set the LoPyList property. When you call Add(), the setter does not run and so RaisePropertyChangedEvent is never called and your binding is never updated.
What you could do is add:
RaisePropertyChangedEvent("LoPyList"); after you call Add():
public void Cmd_OnDataReceivedDeserialized(RequestResponse response)
{
LoPyList.Add(new LoPy() { name = "Test", id = "00" });
RaisePropertyChangedEvent(nameof(LoPyList));
}
Alternatively, ObservableCollection exposes a CollectionChanged event that
Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.
You can hook into that event and then call RaisePropertyChangedEvent("LoPyList");
It turned out that I initialize the viewmodel twice and use the second viewmodel that is not connected to the view. Afterward I used the dispatcher and it works like a charm.
Thanks for the help!

Fast changing collection MVVM WPF - high CPU usage & UI almost freezes

I am developing an app with a datagrid of that displays certain running Windows processes (in my example Chrome processes).
The datagrid is loaded with processes when a checkbox is checked.
Requirements:
Display 'live' info for the name, memory usage (private working set) of each process, just like in the Windows Task Manager - Processes tab.
Monitor for processes that exit and remove them from the datagrid.
Monitor for certain processes that start.
Used techniques:
MVVM
MVVM Light
BenoƮt Blanchon approach for fast changing properties
Thomas Levesque AsyncObservableCollection to modify an ObservableCollection from another thread
Issue(s):
When the processes are loaded, the CPU usage gets very high and the UI almost freezes.
CPU usage remains high even when the ManagerService.Stop() is called.
Sometimes a System.InvalidOperationException - Cannot change ObservableCollection during a CollectionChanged event exception is thrown when a process is removed from the collection.
How can I fix this issues? Also is my approach a 'good practice' one?
Any help would be greatly appreciated! I've already spent a lot of time on this issue.
Update 1
Didn't help, removing OnRendering() and implementing INotifyPropertyChanged
public class CustomProcess : INotifyPropertyChanged
{
private double _memory;
public double Memory
{
get { return _memory; }
set
{
if (_memory != value)
{
_memory = value;
OnPropertyChanged(nameof(Memory));
}
}
}
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
if (_isChecked != value)
{
_isChecked = value;
OnPropertyChanged(nameof(IsChecked));
}
}
Update 2
Following Evk advice I've updated
Used regular ObservableCollection
moved timer to viewmodel
CPU usage is much lower now.
However I sometimes get an Process with an ID of ... is not running exception in the OnProcessStarted()
Viewmodel
public class MainViewModel
{
System.Threading.Timer timer;
private ObservableCollection<CustomProcess> _processes;
public ObservableCollection<CustomProcess> Processes
{
get
{
if (_processes == null)
_processes = new ObservableCollection<CustomProcess>();
return _processes;
}
}
private void OnBooleanChanged(PropertyChangedMessage<bool> propChangedMessage)
{
if (propChangedMessage.NewValue == true)
{
_managerService.Start(_processes);
timer = new System.Threading.Timer(OnTimerTick, null, 0, 200); //every 200ms
ProcessesIsVisible = true;
}
else
{
timer.Dispose();
_managerService.Stop();
ProcessesIsVisible = false;
}
}
private void OnTimerTick(object state)
{
try
{
for (int i = 0; i < Processes.Count; i++)
Processes[i].UpdateMemory();
}
catch (Exception)
{
}
}
Model
public class CustomProcess : INotifyPropertyChanged
{
public void UpdateMemory()
{
if (!ProcessObject.HasExited)
Memory = Process.GetProcessById(ProcessObject.Id).PagedMemorySize64;
}
private double _memory;
public double Memory
{
get { return _memory; }
set
{
if (_memory != value)
{
_memory = value;
OnPropertyChanged(nameof(Memory));
}
}
}
Service
private void OnProcessNotification(NotificationMessage<Process> notMessage)
{
if (notMessage.Notification == "exited")
{
_processes.Remove(p => p.ProcessObject.Id == notMessage.Content.Id, DispatcherHelper.UIDispatcher);
}
}
Original code
XAML
<DataGrid ItemsSource="{Binding Processes}">
<DataGridTextColumn Header="Process name"
Binding="{Binding ProcessObject.ProcessName}"
IsReadOnly='True'
Width='Auto' />
<DataGridTextColumn Header="PID"
Binding="{Binding ProcessObject.Id}"
IsReadOnly='True'
Width='Auto' />
<DataGridTextColumn Header="Memory"
Binding='{Binding Memory}'
IsReadOnly='True'
Width='Auto' />
</DataGrid>
XAML Code behind
public MainWindow()
{
InitializeComponent();
DataContext = SimpleIoc.Default.GetInstance<MainViewModel>();
CompositionTarget.Rendering += OnRendering;
}
private void OnRendering(object sender, EventArgs e)
{
if (DataContext is IRefresh)
((IRefresh)DataContext).Refresh();
}
}
ViewModel
public class MainViewModel : Shared.ViewModelBase, IRefresh
{
private AsyncObservableCollection<CustomProcess> _processes;
public AsyncObservableCollection<CustomProcess> Processes
{
get
{
if (_processes == null)
_processes = new AsyncObservableCollection<CustomProcess>();
return _processes;
}
}
private readonly IManagerService _managerService;
public MainViewModel(IManagerService managerService)
{
_managerService = managerService;
Messenger.Default.Register<PropertyChangedMessage<bool>>(this, OnBooleanChanged);
}
#region PropertyChangedMessage
private void OnBooleanChanged(PropertyChangedMessage<bool> propChangedMessage)
{
if (propChangedMessage.NewValue == true)
{
_managerService.Start(_processes);
}
else
{
_managerService.Stop();
}
}
public void Refresh()
{
foreach (var process in Processes)
RaisePropertyChanged(nameof(process.Memory)); //notify UI that the property has changed
}
Service
public class ManagerService : IManagerService
{
AsyncObservableCollection<CustomProcess> _processes;
ManagementEventWatcher managementEventWatcher;
public ManagerService()
{
Messenger.Default.Register<NotificationMessage<Process>>(this, OnProcessNotification);
}
private void OnProcessNotification(NotificationMessage<Process> notMessage)
{
if (notMessage.Notification == "exited")
{
//a process has exited. Remove it from the collection
_processes.Remove(p => p.ProcessObject.Id == notMessage.Content.Id);
}
}
/// <summary>
/// Starts the manager. Add processes and monitor for starting processes
/// </summary>
/// <param name="processes"></param>
public void Start(AsyncObservableCollection<CustomProcess> processes)
{
_processes = processes;
_processes.CollectionChanged += OnCollectionChanged;
foreach (var process in Process.GetProcesses().Where(p => p.ProcessName.Contains("chrome")))
_processes.Add(new CustomProcess(process));
MonitorStartedProcess();
Task.Factory.StartNew(() => MonitorLogFile());
}
/// <summary>
/// Stops the manager.
/// </summary>
public void Stop()
{
_processes.CollectionChanged -= OnCollectionChanged;
managementEventWatcher = null;
_processes = null;
}
private void MonitorLogFile()
{
//this code monitors a log file for changes. It is possible that the IsChecked property of a CustomProcess object is set in the Processes collection
}
/// <summary>
/// Monitor for started Chrome
/// </summary>
private void MonitorStartedProcess()
{
var qStart = "SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName like '%chrome%'";
ManagementEventWatcher managementEventWatcher = new ManagementEventWatcher(new WqlEventQuery(qStart));
managementEventWatcher.EventArrived += new EventArrivedEventHandler(OnProcessStarted);
try
{
managementEventWatcher.Start();
}
catch (Exception)
{
}
}
private void OnProcessStarted(object sender, EventArrivedEventArgs e)
{
try
{
int pid = Convert.ToInt32(e.NewEvent.Properties["ProcessID"].Value);
_processes.Add(new CustomProcess(Process.GetProcessById(pid))); //add to collection
}
catch (Exception)
{
}
}
Model
public class CustomProcess
{
public Process ProcessObject { get; }
public CustomProcess(Process process)
{
ProcessObject = process;
try
{
ProcessObject.EnableRaisingEvents = true;
ProcessObject.Exited += ProcessObject_Exited;
Task.Factory.StartNew(() => UpdateMemory());
}
catch (Exception)
{
}
}
private void ProcessObject_Exited(object sender, EventArgs e)
{
Process process = sender as Process;
NotificationMessage<Process> notMessage = new NotificationMessage<Process>(process, "exited");
Messenger.Default.Send(notMessage); //send a notification that the process has exited
}
private void UpdateMemory()
{
while (!ProcessObject.HasExited)
{
try
{
Memory = Process.GetProcessById(ProcessObject.Id).PagedMemorySize64;
}
catch (Exception)
{
}
}
}
private double _memory;
public double Memory
{
get { return _memory; }
set
{
if (_memory != value)
{
_memory = value;
}
}
}
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
if (_isChecked != value)
{
_isChecked = value;
}
}
}
Writing to a GUI is expensive. If you only do it once per user triggered event you will not notice it. But once you write from any kind of loop - including a loop running on another thread - you will notice it. I even wrote some example code for Windows Forms to showcase this:
using System;
using System.Windows.Forms;
namespace UIWriteOverhead
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
int[] getNumbers(int upperLimit)
{
int[] ReturnValue = new int[upperLimit];
for (int i = 0; i < ReturnValue.Length; i++)
ReturnValue[i] = i;
return ReturnValue;
}
void printWithBuffer(int[] Values)
{
textBox1.Text = "";
string buffer = "";
foreach (int Number in Values)
buffer += Number.ToString() + Environment.NewLine;
textBox1.Text = buffer;
}
void printDirectly(int[] Values){
textBox1.Text = "";
foreach (int Number in Values)
textBox1.Text += Number.ToString() + Environment.NewLine;
}
private void btnPrintBuffer_Click(object sender, EventArgs e)
{
MessageBox.Show("Generating Numbers");
int[] temp = getNumbers(10000);
MessageBox.Show("Printing with buffer");
printWithBuffer(temp);
MessageBox.Show("Printing done");
}
private void btnPrintDirect_Click(object sender, EventArgs e)
{
MessageBox.Show("Generating Numbers");
int[] temp = getNumbers(1000);
MessageBox.Show("Printing directly");
printDirectly(temp);
MessageBox.Show("Printing done");
}
}
}
Your code is even slightly worse, as you allow the Update and thus Layout code to run between each update. While it does keep the UI responsive, it is more code to run.
You will not get around limiting the updates. I would put these kinds of Limitations clearly on the View Side. Personally I prefer this way:
Do not register the Change Notificaiton events realted to the Observable collection
Make a timer that regularly updates the UI with the current value of the Collection. Set the timer to something like 60 Updates per second. That should be fast enough for humans.
You may want to add some form of Locking to the code writing the Collection and the accessor code to avoid race conditions.
A few side notes:
A pet Peeve of mine is Exception Hanlding. And I see some swallowing of Fatal Exceptions there. You really should fix that ASAP. It is bad enough that Threads can accidentally swallow exceptions, you should not write additional code for this. Here are two articles I link a lot: http://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx | http://www.codeproject.com/Articles/9538/Exception-Handling-Best-Practices-in-NET
Secondly, ObservableColelctions are notoriously bad with complete reworks. It lacks a add-range Function. So every single change will trigger an update. My usual workaround is:
1. Give the property exposing the Collection Change Notification
2. Do not work with the exposed collection on any update.
3. Instead work with a background collection. Only when this new state is finished, do you expose it.
Instead of you updating/refresing the UI yourself, make use of the WPF change notification system achieved using DataBinding & PropertyChanged event.
As MSDN quotes -
The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
For example, consider a Person object with a property called FirstName. To provide generic property-change notification, the Person type implements the INotifyPropertyChanged interface and raises a PropertyChanged event when FirstName is changed.
More details here.

Async call + event + dispatcher = unexpected behavior

I have a mysterious problem, which I can't reproduce on my PC, but it happens on other PCs.
Here is a simplified cut which shows the problem
class SomeViewModel
{
Item _item;
Job _job = new Job();
public SomeViewModel()
{
_job.Progress += (s, e) => App.Current.Dispatcher.Invoke(() => Progress());
}
async void DoSomething()
{
...
SomeItem.SomeProperty = newValue; // this proves SomeItem is not null
_item = SomeItem;
var result = await Task<bool>.Run(() => _job.Do());
_item = null;
...
}
void Progress()
{
if(_item == null) { ... } // sometimes true, why???
...
}
}
class Job
{
public event EventHandler Progress;
public bool Do()
{
...
Progress?.Invoke(this, EventArgs.Empty); // in cycle
...
return true;
}
}
DoSomething is called from button command. Idea is to store current item in the field and access it within event handler. Event is never called before item is set to something and is never called after item is set to null.
My question: how is it possible for _item == null in event handler?
It will either never happens (my PC) or happens every time (other PC). And I can't understand what is wrong. Any ideas?

equivalent to silverlight busyindicator in MVC

in silverlight and MVVM , I can fire the busy indicator for example
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private bool _isBusy;
public bool IsBusy
{
get
{
return _isBusy;
}
set
{
_isBusy = value;
RaisePropertyChanged("IsBusy");
}
}
ObervableCollection<OperationBase> _pendingOperations = new ObervableCollection<OperationBase>();
public ViewModelBase()
{
_pendingOperations.CollectionChanged +=(s,e)=>
{
if( _pendingOperations.Count > 0)
IsBusy = true // has operation going on
else
IsBusy = false; //no operation
}
}
void LoadData()
{
LoadOperation op = Context.Load(YourQuery, lo=>
{
_pendingOperations.Remove(lo); // lo is the operation, remove it
if (lo.HasError)
{
lo.MarkErrorAsHandled();
MessageBox.Show(lo.Error.Message);
}
});
_pendingOperations.Add(op);// add operation to the collection
}
void SaveData()
{
SubmitOperation so = this.context.SubmitChanges(s =>
{
_pendingOperations.Remove(s);
if (s.HasError)
{
s.MarkErrorAsHandled();
MessageBox.Show(s.Error.Message);
}
}, null);
_pendingOperations.Add(so);// add operation to the collection }
}
...
}
I want to do the same in MVC , any idea how to achieve that for example on search , create or any long process I need to show busy indicator and close it at the end , I know there's no property changed , am wonder if ther's any way
Assumeing you mean MVC -> ASP.NET MVC or something HTML/Javascript based with some sort of web server component.
In general you would have an animated gif (A spinning wheel for example) and show hide them while you are waiting for a long running operation.
In Javascript you can take advantage of Promises or use generic callback functions.
//pseudo code
loadData(function(data){
// data came back async
// do something with data
$('#loader').hide();
});
$('#loader').show();
or with promises
//method should return promise
var promise = loadData();
$('#loader').show();
promise.done(function(data){
// do something with data
$('#loader').hide();
});
you should of course do handle the error case too but same principles apply....

Check/Solve cross thread operation

There is a StatusProgressBar, which is often accessed from threads. To ensure this, its Text property is as follow:-
[Browsable(true)]
public override string Text
{
get
{
return prg.Text ;
}
set
{
prg.Text = value;
}
}
And the prg.Text = value looks like this
public override string Text
{
get
{
return base.Text;
}
set
{
Misc.CrossThread(this , delegate
{
base.Text = value;
}, true);
}
public static void CrossThread(Control control, MethodInvoker d, bool forceSynchronous)
{
if (control.InvokeRequired)
{
if (forceSynchronous)
{
control.Invoke((Action)delegate { CrossThread(control, d, forceSynchronous); });
}
else
{
control.BeginInvoke((Action)delegate { CrossThread(control, d, forceSynchronous); });
}
}
else
{
if (control.IsDisposed)
{
throw new ObjectDisposedException("Control is already disposed.");
}
d();
}
}
Now the problem is, when its accessed from UI thread (sometimes), the text doesn't change..
ex.
if (cbGateway.SelectedIndex == -1)
{ bp.Text = "Select GATEWAY"; return; }
here 'bp' refers to StatusProgressBar object.
Now if I put a breakpoint at
{ bp.Text = "Select GATEWAY"; return; }
and then continue, everything happens as expected, text changes..
Why the text isn't changed first time?
If you access the progressbar from the UI thread during a long running task, the progressbar will be not refreshed until that long running task is finished.
Did you try doing the following in your CrossThread method?
Application.DoEvents();

Categories

Resources