0

Suppose a UI update takes quite a long time. As an example, this dummy label takes 1 second to change the text.

public class MyLabel : Label
{
    public override string Text
    {
        get { return base.Text; }
        set
        {
            Debug.WriteLine("Setting " + value);
            Thread.Sleep(1000);
            base.Text = value;
        }
    }
}

If there are multiple tasks who want to update it, it seems those posts are queued.

var UIContext = SynchronizationContext.Current;

for (int i = 0; i < 10; i++)
{
    Task.Factory.StartNew((id) =>
    {
        Debug.WriteLine(id);
        Thread.Sleep((int)id*100);
        UIContext.Post((label) =>
        {
            myLabel1.Text = label.ToString();
        }, id);
    }, i);
}

It seems the label receives the posts are queued, and the label receives them all eventually, but in this example, the next one overwrites the previous one so the previous one needs not be handled. Ideally, I would like to disregard all posts other than the last one. Can I see the queued items and remove them?

Damn Vegetables
  • 11,484
  • 13
  • 80
  • 135
  • 1
    You are asking for a time-machine. They are as difficult to build in software as they are in real life. It is otherwise a very plain problem. A user interface that cannot keep up with the job it has to do is an unusable user interface. The user loses control over the program, can't even close it anymore without resorting to Task Manager. So this is a *bug*, it needs to be fixed. – Hans Passant Feb 10 '17 at 10:34
  • Shouldn't you be using `Progress` instead of posting to the synchronization context? – Panagiotis Kanavos Feb 10 '17 at 11:07

1 Answers1

0

I think I thought of a wrong direction. Instead of dealing with the SynchronizationContext queue, I created my own queue, and it seems to work as I expected.

public class MyLabel : Label
{
    SynchronizationContext UIContext;
    List<string> TextQueue;
    public MyLabel()
    {
        UIContext = SynchronizationContext.Current;
        TextQueue = new List<string>();
    }

    public override string Text
    {
        get { return base.Text; }
        set
        {
            Debug.WriteLine("Setting " + value);
            Thread.Sleep(1000);
            base.Text = value;
        }
    }

    public void QueueTextChange(string text)
    {
        Debug.WriteLine("Request: " + text);
        lock (TextQueue)
        {
            TextQueue.Add(text);
        }
        UIContext.Post((x) => { SetTextFromQueue();}, null);
    }

    void SetTextFromQueue()
    {
        string last=null;
        lock (TextQueue)
        {
            if (TextQueue.Count > 0)
            {
                last = TextQueue.Last();
                TextQueue.Clear();
            }
        }
        if (last != null)
            Text = last;
    }
}

And other threads do not directly call myLabel1.Text = "", but call a special queueing method instead, like the following.

myLabel1.QueueTextChange(label.ToString());
Damn Vegetables
  • 11,484
  • 13
  • 80
  • 135
  • Or you could use one of the built-in features, eg Rx's [Throttle](http://www.introtorx.com/Content/v1.0.10621.0/13_TimeShiftedSequences.html#Throttle) to limit the rate of events, or ConcurrentCollection to receive the messages. `Progress` to report them in the first place without taking a dependency on a specific synchronization context – Panagiotis Kanavos Feb 10 '17 at 11:06
  • I did not know `Progress` existed. I will look into it. Thank you for your suggestion. – Damn Vegetables Feb 10 '17 at 11:16