Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

History of the problem

This is continuation of my previous question

How to start a thread to keep GUI refreshed?

but since Jon shed new light on the problem, I would have to completely rewrite original question, which would make that topic unreadable. So, new, very specific question.

The problem

Two pieces:

  • CPU hungry heavy-weight processing as a library (back-end)
  • WPF GUI with databinding which serves as monitor for the processing (front-end)

Current situation -- library sends so many notifications about data changes that despite it works within its own thread it completely jams WPF data binding mechanism, and in result not only monitoring the data does not work (it is not refreshed) but entire GUI is frozen while processing the data.

The aim -- well-designed, polished way to keep GUI up to date -- I am not saying it should display the data immediately (it can skip some changes even), but it cannot freeze while doing computation.

Example

This is simplified example, but it shows the problem.

XAML part:

    <StackPanel Orientation="Vertical">
        <Button Click="Button_Click">Start</Button>
        <TextBlock Text="{Binding Path=Counter}"/>
    </StackPanel>

C# part (please NOTE this is one piece code, but there are two sections of it):

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 repost them as answers)

I sorted the list according how much I like the workaround, i.e. how much work it requires, limitations of it, etc.

  1. this is mine, it is ugly, but simplicity of it kills -- before sending notification freeze a thread -- Thread.Sleep(1) -- to let the potential receiver "breathe" -- it works, it is minimalistic, it is ugly though, and it ALWAYS slows down computation even if no GUI is there
  2. based on Jon idea -- give up with data binding COMPLETELY (one widget with databinding is enough for jamming), and instead check from time to time data and update the GUI manually -- well, I didn't learn WPF just to give up with it now ;-)
  3. Thomas idea -- insert proxy between library and frontend which would receiver all notifications from the library, and pass some of them to WPF, like for example every second -- the downside is you have to duplicate all objects that send notifications
  4. based on Jon idea - pass GUI dispatcher to library and use it for sending notifications -- why it is ugly? because it could be no GUI at all

My current "solution" is adding Sleep in the main loop. The slowdown is negligible, but it is enough for WPF to be refreshed (so it is even better than sleeping before each notification).

I am all ears for real solutions, not some tricks.

Remarks

Remark on giving up with databinding -- for me the design of it is broken, in WPF you have single channel of communication, you cannot bind directly to the source of the change. The databinding filters the source based on name (string!). This requires some computation even if you use some clever structure to keep all the strings.

Edit: Remark on abstractions -- call me old timer, but I started learning computer convinced, that computers should help humans. Repetitive tasks are domain of computers, not humans. No matter how you call it -- MVVM, abstractions, interface, single inheritance, if you write the same code, over and over, and you don't have way to automatize the things you do, you use broken tool. So for example lambdas are great (less work for me) but single inheritance is not (more work for me), data binding (as an idea) is great (less work) but the need of proxy layer for EVERY library I bind to is broken idea because it requires a lot of work.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
96 views
Welcome To Ask or Share your Answers For Others

1 Answer

In my WPF applications I don't send the property change directly from the model to the GUI. It always goes via a proxy (ViewModel).

The property change events are put in a queue which is read from the GUI thread on a timer.

Don't understand how that can be so much more work. You just need another listener for your model's propertychange event.

Create a ViewModel class with a "Model" property which is your current datacontext. Change the databindings to "Model.Property" and add some code to hook up the events.

It looks something like this:

public MyModel Model { get; private set; }

public MyViewModel() {
    Model = new MyModel();
    Model.PropertyChanged += (s,e) => SomethingChangedInModel(e.PropertyName);
}

private HashSet<string> _propertyChanges = new HashSet<string>();

public void SomethingChangedInModel(string propertyName) {
    lock (_propertyChanges) {
        if (_propertyChanges.Count == 0)
            _timer.Start();
        _propertyChanges.Add(propertyName ?? "");
    }
}

// this is connected to the DispatherTimer
private void TimerCallback(object sender, EventArgs e) {
    List<string> changes = null;
    lock (_propertyChanges) {
        _Timer.Stop(); // doing this in callback is safe and disables timer
        if (!_propertyChanges.Contain(""))
            changes = new List<string>(_propertyChanges);
        _propertyChanges.Clear();
    }
    if (changes == null)
        OnPropertyChange(null);
    else
        foreach (string property in changes)
            OnPropertyChanged(property);
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...