Problem history
This is a continuation of my previous question.
How to start a thread to update the GUI?
but since John shed new light on the problem, I would have to completely rewrite the original question that would make this topic unreadable. So, a new, very specific question.
Problem
Two parts:
- CPU hungry heavy processing as a library (back-end)
- WPF GUI with data binding, which serves as a monitor for processing (front-end)
The current situation - the library sends so many notifications about data changes, which, despite the fact that it works within its flow, completely hides the WPF data binding mechanism, and as a result, not only does the data monitoring not work (it does not update), but also The entire GUI is frozen during data processing.
The goal is a well-thought-out, polished way to constantly update the graphical interface - I am not saying that it should immediately display data (it may even skip some changes), but it cannot freeze when performing calculations.
Example
This is a simplified example, but it shows a problem.
XAML part:
<StackPanel Orientation="Vertical"> <Button Click="Button_Click">Start</Button> <TextBlock Text="{Binding Path=Counter}"/> </StackPanel>
Part C # (please note that this is the code of one part, but there are two sections):
public partial class MainWindow : Window,INotifyPropertyChanged { // GUI part public MainWindow() { InitializeComponent(); DataContext = this; } private void Button_Click(object sender, RoutedEventArgs e) { var thread = new Thread(doProcessing); thread.IsBackground = true; thread.Start(); } // this is non-GUI part -- do not mess with GUI here public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string property_name) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(property_name)); } long counter; public long Counter { get { return counter; } set { if (counter != value) { counter = value; OnPropertyChanged("Counter"); } } } void doProcessing() { var tmp = 10000.0; for (Counter = 0; Counter < 10000000; ++Counter) { if (Counter % 2 == 0) tmp = Math.Sqrt(tmp); else tmp = Math.Pow(tmp, 2.0); } } }
Known workarounds
(Please do not send them as responses)
I sorted the list according to how much I like the workaround, i.e. how much work is required, its limitations, etc.
- This is mine, it is ugly, but the simplicity kills it - before sending a notification to freeze the thread - Thread.Sleep (1) - so that the potential receiver "breathes" - it works, it is minimalistic, it is ugly, and it ALWAYS slows down the calculation, even if the graphical interface absent
- based on John’s idea - to abandon data binding COMPLETELY (one widget with data binding is enough to jam), and instead check the data from time to time and update the GUI manually - I did not teach WPF just to refuse it now; -)
- Thomas's idea is to insert a proxy between the library and the interface, which will receive all notifications from the library and send some of them to WPF, for example, every second - the disadvantage is that you have to duplicate all objects sending notifications
- based on Jon’s idea - pass the GUI manager to the library and use it to send notifications - why is it ugly? because there can be no GUI at all
My current “solution” adds Sleep to the main loop. The slowdown is slight, but enough to keep WPF up to date (so it's even better than sleeping before each notification).
I'm all ears for real solutions, not some tricks.
Notes
A note about refusing data binding - for me its design is broken, in WPF you have a single communication channel, you cannot directly bind to the source of the change. Data binding filters the source based on the name (string!). This requires some computation, even if you use some kind of smart structure to save all the lines.
Edit: A note on abstractions - give me an old timer, but I began to study the computer, convinced that computers should help people. Repetitive tasks are an area of computers, not people. No matter what you call it - MVVM, abstractions, interface, one inheritance, if you write the same code again and again, and you have no way to automate what you do, you use a broken tool. So, for example, lambdas are excellent (less work for me), but inheritance is not (more work for me), data binding (as an idea) is great (less work), and the need for a proxy level for EVERY library that I link - This is a broken idea because it requires a lot of work.