1

This code imitates the problem that I am experiencing in my app.

    private void button1_Click(object sender, EventArgs e)
    {
        button1.BackColor = Color.Red;
        Thread.Sleep(3000);
        button1.BackColor = Color.Green;
    }

I would expect this code to;

  • Make button red
  • Wait 3s
  • Make button green

but instead It is waiting 3s and then making button green. I can't just make the button green from the start as this would not work in my bigger app.

Does anyone have any idea what is wrong and also how i could fix it? Thanks in advance.

  • 3
    Your GUI thread has no chance to update the UI to use the red color. Try using async programming model and Task.Delay. For now just add a Thread.Sleep(0) before the 3 second sleep. I Think that should work. – Creepin Nov 11 '18 at 22:22
  • 1
    Possible duplicate of [Thread.Sleep() without freezing the UI](https://stackoverflow.com/questions/24136390/thread-sleep-without-freezing-the-ui) – mjwills Nov 11 '18 at 22:26
  • Painting the screen does not happen immediately, it happens as an event in the event queue. Events are only processed after you return from the current event. So the button believes it is red, but it can never paint to the screen because you did not return, and give the "paint" event a chance. – Ben Nov 11 '18 at 22:26
  • Possible duplicate of [How to put delay before doing an operation in WPF](https://stackoverflow.com/questions/15599884/how-to-put-delay-before-doing-an-operation-in-wpf) – Creepin Nov 11 '18 at 22:37

3 Answers3

3

No. it's changing to Red but you are blocking your UI thread and thus you don't see the color changed. What if you change the handler to a async one like

private async Task button1_Click(object sender, EventArgs e)
{
    button1.BackColor = Color.Red;
    await Task.Delay(3000);
    button1.BackColor = Color.Green;
}
Rahul
  • 76,197
  • 13
  • 71
  • 125
2

The problem is that with Sleep you are blocking the main (rendering) thread. So you set the button red, but because you are blocking the thread, the app can't render it. In fact, I expect that the whole application freeze.
I am not sure what are you using, but try to look at some timers.

EDIT or simply use tasks Delayed function calls. Just do not use threads, please.

Patrik Valkovič
  • 706
  • 7
  • 26
0

The technical reason for this, is the UI works on a Message Pump (in the case of WinForms, or similar in regards to WPF). Basically a message pump is a queue of work items to do, and a while loop like the following:

while(GetMessage(&msg, NULL, 0, 0) > 0) 
{ 
  TranslateMessage(&msg); 
  DispatchMessage(&msg); 
}

What is happening, is that when you are processing your click:

  1. The click goes into the message queue
  2. Gets processed by the message pump
  3. Goes into your click method
  4. Blocks the thread for x amount of seconds, its still in your method and the pump can't process.

Why?

Thread.Sleep()

Suspends the current thread for the specified amount of time.

In short, no other messages get processed. In your example you sleep the thread before the pump has had time to process your colour change yet (its still stick processing your click).

When the messages eventually process, it goes through the backlog (your color changes being part of that), then without pause quickly changes it from one to the other.

The fix is quite simple, you need to allow your pump to process messages while you wait and as the other answers have eluded to, in modern version on .net we can use the async await pattern, and take advantage of the Task.Delay() method.

When the program process anything that is prefixed with await,

  1. If you are on the UI thread it captures the context
  2. Starts another thread with a continuation,
  3. lets the message pump continue processing.
  4. When the task has finished (and your delay is over), it returns control back to the original context (the thread you called it from).

Hey presto. Everything is as it should be.

halfer
  • 19,824
  • 17
  • 99
  • 186
TheGeneral
  • 79,002
  • 9
  • 103
  • 141