C# WhenAny Cancel call not working causing thread accumulation - c#

It's most likely a really stupid mistake but I cannot figure this out and I've spent almost 2 days on this.
I have an app that with a button click launches 5 tasks running in parallel but each with its own delay. This part works fine.
However, if you click the same button again, it doesn't cancel previously launched tasks and creates another instance. So basically creates more and more tasks that run in parallel. I obviously have code trying to cancel tasks if the button is clicked more than once but for some reason it doesn't work.
Could someone point me to my issue? Or is this code is beyond repair and needs total revamp? Thank you!
If you execute this WPF app and click on the button, you will see that ping frequency just keeps on increasing.
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace TestMultiThreadWithDiffSleeps
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
#region Binding
private string m_output0;
public string Output0
{
get { return m_output0; }
set { m_output0 = value; OnPropertyChanged(); }
}
private string m_output1;
public string Output1
{
get { return m_output1; }
set { m_output1 = value; OnPropertyChanged(); }
}
private string m_output2;
public string Output2
{
get { return m_output2; }
set { m_output2 = value; OnPropertyChanged(); }
}
private string m_output3;
public string Output3
{
get { return m_output3; }
set { m_output3 = value; OnPropertyChanged(); }
}
private string m_output4;
public string Output4
{
get { return m_output4; }
set { m_output4 = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private static SemaphoreSlim ThreadSemaphore;
private CancellationTokenSource CancellationTokenSrc;
public MainWindow()
{
InitializeComponent();
DataContext = this;
ThreadSemaphore = new SemaphoreSlim(1, 1);
}
private async void ButtonStart_Click(object sender, RoutedEventArgs e)
{
await StartToMonitor();
}
private async Task<bool> StartToMonitor()
{
M_Stop(); // Stop everything in case this is a restart
CancellationTokenSrc = new CancellationTokenSource();
bool taskResult = await M_Start();
CancellationTokenSrc = null;
return taskResult;
}
public void M_Stop()
{
Output0 = string.Empty;
Output1 = string.Empty;
Output2 = string.Empty;
Output3 = string.Empty;
Output4 = string.Empty;
if (CancellationTokenSrc != null)
CancellationTokenSrc.Cancel();
}
private async Task<bool> M_Start()
{
List<Task<bool>> tasks = new List<Task<bool>>();
// Build task list
for (int i = 0; i < 5; i++)
{
int iIdx = i; // Need to do this when multi-threading
int sleepTime = (i + 1) * 1000;
tasks.Add(Task.Run(async () =>
{
while (!CancellationTokenSrc.Token.IsCancellationRequested)
{
if (!Ping(iIdx))
return false; // Ping in this example always returns 'true' but you can imagine in real life there'd be 'false' returns that shall cancel all threads
await Task.Delay(sleepTime); // Delay for different length of time for each thread
}
return true;
}, CancellationTokenSrc.Token));
}
Task<bool> firstFinishedTask = await Task.WhenAny(tasks);
bool result = firstFinishedTask.Result;
CancellationTokenSrc.Cancel(); // Cancel all other threads as soon as one returns
return result;
}
private bool Ping(int index)
{
ThreadSemaphore.Wait(); // Not needed for this app... here only because it's in my other app I'm troubleshooting
switch (index)
{
case 0: Output0 += "*"; break;
case 1: Output1 += "*"; break;
case 2: Output2 += "*"; break;
case 3: Output3 += "*"; break;
case 4: Output4 += "*"; break;
}
ThreadSemaphore.Release();
return true;
}
}
}
<Window x:Class="TestMultiThreadWithDiffSleeps.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" SizeToContent="WidthAndHeight">
<StackPanel>
<Button Content="Start" HorizontalAlignment="Left" VerticalAlignment="Top" Width="200" Margin="0,30,0,0" Click="ButtonStart_Click"/>
<TextBox Text="{Binding Output0}"/>
<TextBox Text="{Binding Output1}"/>
<TextBox Text="{Binding Output2}"/>
<TextBox Text="{Binding Output3}"/>
<TextBox Text="{Binding Output4}"/>
</StackPanel>
</Window>

There are a couple of issues in this code.
The source of the problem you're describing is this:
tasks.Add(Task.Run(async () =>
{
while (!CancellationTokenSrc.Token.IsCancellationRequested)
The tasks always check the token of the current CancellationTokenSrc. So unless the cancellation happens in the exact moment between Task.Delay calls where the tasks check IsCancellationRequested, they'll just see the new, uncancelled CTS you create after a restart.
You should pass the current CancellationToken as a method argument or store it in a local variable inside M_Start, instead of checking the shared CancellationTokenSrc field to avoid this problem.
Slight improvement: Task.Delay also has an overload that accepts a CancellationToken. It will throw a TaskCanceledException.
Additionally, you're updating the OutputX properties from parallel threads (Task.Run), which then raise the PropertyChanged event that's supposed to update the UI. This kind of cross-thread UI interaction isn't safe, you will need to involve the Dispatcher to make sure the event is raised on the UI thread.
Lastly, the ownership of CancellationTokenSrc is fairly complex and seems ripe for race conditions as multiple concurrent methods use and set it. In case of a restart M_Start could easily cause a NullReferenceException when it tries to cancel CancellationTokenSrc which has already been set to null in StartToMonitor.

I think you need to make Task.Delay cancellable too:
await Task.Delay(sleepTime);
Change it to:
await Task.Delay(sleepTime, CancellationTokenSrc.Token);

Related

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.

Updating Progressbar from Two Tasks

I'm trying to update a progress bar from two separate tasks at the same time. I've tried it a number of ways but haven't had much success. See below full code example. In WPF, add a progressbar, bind Maximum to TextMax and Value to TextProgress and you will notice that the progress bar only fills to about half way.
NOTE: This is not my actual solution, just a sample of what I am doing that shows the issue that I threw together, please ignore code style/pattern issues.
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace DelegateTesting
{
public partial class MainWindow : Window
{
ResultsItemViewModel _ViewModel = new ResultsItemViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _ViewModel;
TextProcessing();
}
private static void Method1(
Action<int> reportProgress)
{
var progress = 0;
for(int i = 0;i<100;i++)
{
//Thread.Sleep(200);
reportProgress?.Invoke(++progress);
}
}
private static void Method2(
Action<int> reportProgress)
{
var progress = 0;
for (int i = 0; i < 100; i++)
{
//Thread.Sleep(200);
reportProgress?.Invoke(++progress);
}
}
private async Task TextProcessing()
{
_ViewModel.TextMax += 100;
_ViewModel.TextMax += 100;
var dispatcher = Application.Current.Dispatcher;
var reportProgress = dispatcher.MakeInvoker<int>(p => _ViewModel.TextProgress = p);
await Task.WhenAll(
Task.Run(() => Method1(reportProgress)),
Task.Run(() => Method2(reportProgress)));
}
}
public static class DispatcherHelper
{
public static Action<T> MakeInvoker<T>(
this Dispatcher dispatcher,
Action<T> action,
DispatcherPriority priority = DispatcherPriority.Normal)
{
return o => dispatcher.BeginInvoke(priority, action, o);
}
}
public class ResultsItemViewModel : INotifyPropertyChanged
{
int _textProgress, _textMax;
public int TextProgress
{
get => _textProgress;
set
{
_textProgress = value;
NotifyPropertyChanged();
}
}
public int TextMax
{
get => _textMax;
set
{
_textMax = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You are not awaiting TextProcessing. You cannot just call await in the middle. You can either go asynchronous all the way or not at all.
Also, doing work in a constructor is not a good practice.
In order for this to work you have to allow WPF to handle async calls for you. I assume you want to start an action when someone does something, e.g.:
protected async void Button_OnClick(Object sender, EventArgs e)
{
await TextProcessing();
}
You could always bind it to an event that starts a window or something like that.
To be honest your code is very unclear to me, so this might help you understand what you actually need:
int _textProgress, _textMax;
public int TextProgress
{
get => _textProgress;
set
{
_textProgress = value;
NotifyPropertyChanged();
}
}
public int TextMax
{
get => _textMax;
set
{
_textMax = value;
NotifyPropertyChanged();
}
}
protected async void Button_OnClick(Object sender, EventArgs e)
{
TextMax = 0;
var t1 = Task.Run(() => {
TextProgress += 50;
});
var t2 = Task.Run(() => {
TextProgress += 50;
});
await Task.WhenAll(t1, t2);
}
And in your view you should have some kind of button with command for Button_OnClick and progress bar:
<ProgressBar Maximum="100" Height="50" Value="{Binding TextProgress}"></ProgressBar>
One more thing. It looks like you have a ViewModel, but you are doing work in a view. You should move that logic to your ViewModel.

Use ICommand in WPF

What is the best way to use commands in WPF ?
I use some commands, thoses commands can take a time to execute. I want that my application not freeze while running but I want the features to be disabled.
there is my MainWindow.xaml :
<Window ...>
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Button Grid.Row="0"
Grid.Column="0"
Style="{StaticResource StyleButton}"
Content="Load"
Command="{Binding LoadCommand}"/>
<Button Grid.Row="0"
Grid.Column="1"
Style="{StaticResource StyleButton}"
Content="Generate"
Command="{Binding GenerateCommand}"/>
</Grid>
</Window>
and my MainViewModel.cs :
public class MainViewModel : ViewModelBase
{
#region GenerateCommand
#endregion
#region Load command
private ICommand _loadCommand;
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
_loadCommand = new RelayCommand(OnLoad, CanLoad);
return _loadCommand;
}
}
private void OnLoad()
{
//My code
}
private bool CanLoad()
{
return true;
}
#endregion
}
I saw a solution with background worker but I don't know how to use it. And I wonder if I should create one instance by command.
Is there a cleaner/best way ?
I want that my application not freeze while running but I want the features to be disabled.
The key to prevent the application from freezing is to perform any long-running operation on a background thread. The easiest way to do this is to start a Task. To disable the window you could bind its IsEnabled property to a source property of the view model that you set prior to starting the task. The following sample code should give you the idea:
public class MainViewModel : ViewModelBase
{
private RelayCommand _loadCommand;
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
_loadCommand = new RelayCommand(OnLoad, CanLoad);
return _loadCommand;
}
}
private void OnLoad()
{
IsEnabled = false;
_canLoad = false;
_loadCommand.RaiseCanExecuteChanged();
Task.Factory.StartNew(()=> { System.Threading.Thread.Sleep(5000); }) //simulate som long-running operation that runs on a background thread...
.ContinueWith(task =>
{
//reset the properties back on the UI thread once the task has finished
IsEnabled = true;
_canLoad = true;
}, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
private bool _canLoad = true;
private bool CanLoad()
{
return _canLoad;
}
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set { _isEnabled = value; RaisePropertyChanged(); }
}
}
Note that you cannot access any UI element from a background thread since controls have thread affinity: http://volatileread.com/Thread/Index?id=1056
My approach to avoid UI freezing in these scenarios is to use async/await in the ICommand execution, and execute the long-running code on a background thread. Your modified code would look something like this:
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
_loadCommand = new RelayCommand(async o => await OnLoadAsync(), CanLoad);
return _loadCommand;
}
}
private async Task OnLoadAsync()
{
await Task.Run(() => MyLongRunningProcess());
}
If that background task needs to update anything bound to the UI then it needs to be wrapped in a Dispatcher.Invoke (or Dispatcher.BeginInvoke).
If you want to prevent the command from being executed a second time just set "CanLoad" to true before the await Task.Run(... line, and back to false after it.
I'd suggest to use Akka.Net: you can find an example with WPF on github.
I've forked it to impement stop and start commands:
my goal was to show bidirectional communication between Akka.Net actors and ViewModel.
You'll find the ViewModel calling the ActorSystem like this
private void StartCpuMethod() {
Debug.WriteLine("StartCpuMethod");
ActorSystemReference.Start();
}
private void StopCpuMethod() {
Debug.WriteLine("StopCpuMethod");
ActorSystemReference.Stop();
}
with an Actor receiving those messages
public CPUReadActor()
{
Receive<ReadCPURequestMessage>(msg => ReceiveReadDataMessage());
Receive<ReadCPUSyncMessage>(msg => ReceiveSyncMessage(msg));
}
private void ReceiveSyncMessage(ReadCPUSyncMessage msg)
{
switch (msg.Op)
{
case SyncOp.Start:
OnCommandStart();
break;
case SyncOp.Stop:
OnCommandStop();
break;
default:
throw new Exception("unknown Op " + msg.Op.ToString());
}
}
and the other way round from an Actor
public ChartingActor(Action<float, DateTime> dataPointSetter)
{
this._dataPointSetter = dataPointSetter;
Receive<DrawPointMessage>(msg => ReceiveDrawPointMessage(msg));
}
private void ReceiveDrawPointMessage(DrawPointMessage msg)
{
_dataPointSetter(msg.Value, msg.Date);
}
to the ViewModel
public MainWindowViewModel()
{
StartCpuCommand = new RelayCommand(StartCpuMethod);
StopCpuCommand = new RelayCommand(StopCpuMethod);
SetupChartModel();
Action<float, DateTime> dataPointSetter = new Action<float, DateTime>((v, d) => SetDataPoint(v, d));
ActorSystemReference.CreateActorSystem(dataPointSetter);
}
private void SetDataPoint(float value, DateTime date)
{
CurrentValue = value;
UpdateLineSeries(value, date);
}
The best way here it's a use of async/await, in my opinion. https://msdn.microsoft.com/ru-ru/library/mt674882.aspx
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
LoadCommand = new RelayCommand(async ol => await OnLoadAsync(), CanLoad);
}
public ICommand LoadCommand { get; }
private async void OnLoadAync()
{
await SomethingAwaitable();
}
private Task<bool> SomethingAwaitable()
{
//Your code
}
}

Disabling controls during processing a function

I have problem with disabling controls in WPF Application during processing a function. It is simple app sending data via serial port. When port is "listening" (SerialPort.ReadChar();) I want all controls to go gray/disable them.
This way:
private void startTransmissionButton_Click(object sender, RoutedEventArgs e)
{
ComboBox.IsEnabled = false;
Button1.IsEnabled = false;
Button2.IsEnabled = false;
Button3.IsEnabled = false;
SerialPort com = new SerialPort("COM1");
com.Open();
c = (char)com.ReadChar();
com.Close();
ComboBox.IsEnabled = true;
Button1.IsEnabled = true;
Button2.IsEnabled = true;
Button3.IsEnabled = true;
}
disabling seems to work only inside the function, so nothing actually happens in the window. When I remove enabling at the end of function all controls go gray, but not at the moment of *.IsEnabled = false instructions, but when the functions ends. Is there something I do wrong or everything is OK and this needs to be done in different way?
Welcome to StackOverflow !
Since your code is synchronous it is blocking, hence the behavior you get. There is also the need to consider using the Dispatcher but luckily in your case you haven't encountered such issue.
Suggestions:
use a ViewModel
bind to some properties in it to enable/disable your UI
doing so separates concerns and simplifies your thing in general
Example : a 5 second work that disables the UI (really simple !)
Points of interest in my code:
by putting all controls that must be disabled within a StackPanel and binding its IsEnabled property the model's IsAvailable property I effectively simplify this process
no controls are modified from code-behind
the view (your window) does nothing more than presenting, all your logic is in a model that is not tied to your window and can be reused somewhere else
XAML:
<Window x:Class="WpfApplication1.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainView"
Width="525"
Height="350"
d:DataContext="{d:DesignInstance wpfApplication1:MainViewModel,
d:IsDesignTimeCreatable=True}"
mc:Ignorable="d">
<Grid>
<StackPanel>
<Button Command="{Binding DoSomeWork}" Content="Do some long work" />
<StackPanel IsEnabled="{Binding IsAvailable}">
<CheckBox Content="Test control 1" />
<RadioButton Content="Test control 2" />
</StackPanel>
<TextBlock Text="Overall progress:" />
<ProgressBar Height="10" Value="{Binding CurrentProgress}" />
</StackPanel>
</Grid>
</Window>
Code-behind:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace WpfApplication1
{
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
// put classes shown below here
}
Your model :
internal class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
// set-up environment
DoSomeWork = new DelegateCommand(DoSomeWorkExecute, DoSomeWorkCanExecute);
IsAvailable = true;
}
public int CurrentProgress
{
get { return _currentProgress; }
set
{
_currentProgress = value;
OnPropertyChanged();
}
}
#region IsAvailable
private bool _isAvailable;
private int _currentProgress;
public bool IsAvailable
{
get { return _isAvailable; }
set
{
_isAvailable = value;
OnPropertyChanged();
}
}
#endregion
#region DoSomeWork
public DelegateCommand DoSomeWork { get; private set; }
private bool DoSomeWorkCanExecute(object arg)
{
return true;
}
private async void DoSomeWorkExecute(object o)
{
await Task.Run(() =>
{
IsAvailable = false;
var steps = 20;
var time = 5000;
var length = time/steps;
for (var i = 0; i < steps; i++)
{
Thread.Sleep(length);
var currentProgress = (int) (((((double) i + 1)*length)/time)*100);
CurrentProgress = currentProgress;
}
IsAvailable = true;
});
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And a trivial command base for DoSomeWork:
internal class DelegateCommand : ICommand
{
private readonly Func<object, bool> _canExecute;
private readonly Action<object> _execute;
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public DelegateCommand(Action<object> execute)
: this(execute, s => true)
{
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public event EventHandler CanExecuteChanged;
}
TODO
Get familiar with:
tasks and asynchronous programming
MVVM for separating concerns, I'd suggest Prism, some would consider this is overkill but it has a very good documentation, there are other players such as MVVM Light
Dispatcher as well
You will experience some pain with these concepts the first time, but over time you will find these are the way to go esp. with WPF.
If you are satisfied with my answer, mark it as the answer, otherwise if you need some clarification then add a comment below and either me or someone will try to help further.
Please, read the full answer Aybe provided. It's always good to follow best practices. But when it comes to small quick test projects, I believe that sometimes it might be an overkill.
If you need quick solution to this problem then you could try to use the following approach:
private async void startTransmissionButton_Click(object sender, RoutedEventArgs e)
{
ComboBox.IsEnabled = false;
Button1.IsEnabled = false;
Button2.IsEnabled = false;
Button3.IsEnabled = false;
await
Task.Factory.StartNew(
() =>
{
SerialPort com = new SerialPort("COM1");
com.Open();
c = (char)com.ReadChar();
com.Close();
}
);
ComboBox.IsEnabled = true;
Button1.IsEnabled = true;
Button2.IsEnabled = true;
Button3.IsEnabled = true;
}
Note that assigning value to c variable happens in another thread.
I hope my answer is helpful.

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

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

Categories

Resources