0

(Edit - This is for learning purposes only the program is completely pointless)

I'm trying to make it look like my program is loading using Thread.Sleep() with a randomly generated number. It works and it waits the time but for some reason it will not display the history variable on screen until after all of the sleeps have happened.

It should do this

  • Print logging in...
  • Sleep for 5-10 seconds
  • print verifying details...
  • sleep for 5-10 seconds
  • print Logged in.

The reason I am appending the history string is because I want to keep all previous prints on the screen and I'm new to programming so I thought this is the easiest way.

        private void Loading()
    {
        Random rnd = new Random();
        int wait1 = rnd.Next(5000, 10000 );
        history = "Logging in...\n";
        historyLbl.Text = history;
        System.Threading.Thread.Sleep(wait1);
        int wait2 = rnd.Next(5000, 10000);
        history = history + "Verifying Details...\n";
        historyLbl.Text = history;
        System.Threading.Thread.Sleep(wait2);
        history = history + "Logged in.\n";
        historyLbl.Text = history;


    }
Ash
  • 21
  • 7
  • 1
    Your `Sleep()` calls block the GUI thread and keep it from updating the display. You'll want to tackle this through a timer or learning about `async`/`await` w/ `Task.Delay()`, as those methods do not block the GUI. – Glorin Oakenfoot Oct 25 '16 at 14:12
  • why would you want your program to load slower? – Liam Oct 25 '16 at 14:13
  • Ah I see, Even so - because I have HistoryLbl.Text before the sleep is used should it not show that before it sleeps the GUI? – Ash Oct 25 '16 at 14:14
  • You're blocking the UI thread, so it won't process your updates to the `Text` field until the Loading() method finishes. One solution (assuming this is `WinForms`) is to use a `BackgroundWorker` to run the body of the code, with updates to the Text field marshalled to the UI thread using `Invoke`. The hacky way (not recommended) is to use calls to `DoEvents` after each update. – Baldrick Oct 25 '16 at 14:14
  • I'm sure this is just a learning exercise. Nothing wrong with doing normally pointless stuff to learn. – Glorin Oakenfoot Oct 25 '16 at 14:14
  • @Liam I'm just trying things out e.g using random generator and sleep just and the appending strings etc. – Ash Oct 25 '16 at 14:15

1 Answers1

2

When you use Thread.Sleep(), it blocks the thread. Like a red light, nothing can move until the block is lifted. In simple Windows Forms applications, the UI thread also runs all of your code. So when you block the thread in a method, you're also blocking the UI.

A nice trick to do something like this is to use the async and await operators, along with Task.Delay()

// Note the change in signature
private async Task Loading()
{
    Random rnd = new Random();
    int wait1 = rnd.Next(5000, 10000 );
    history = "Logging in...\n";
    historyLbl.Text = history;

    await Task.Delay(wait1);

    int wait2 = rnd.Next(5000, 10000);
    history = history + "Verifying Details...\n";
    historyLbl.Text = history;

    await Task.Delay(wait2);

    history = history + "Logged in.\n";
    historyLbl.Text = history;
}

This uses a special language feature that essentially does the waiting on a whole separate thread and comes back to your code when it finishes. That's why the UI won't freeze.

Ok, I'm wrong here. Async and await have allways been a bit mysterious and I guess I just assumed.

Note that anywhere you call this method, you will also need to await it. If you do it on a button click for example, you need to change the button click event handler

// async void is a special pattern for event handlers, to allow them to use async. 
// in general you should always use async Task
private async void Button_Click(object sender, EventArgs e)
{
    await Loading();
}

But the bigger question is why would you ever do this? The user never wants to wait longer than they have to. Every once in a while I use Task.Delay() to allow my UI thread to catch up, but thats only for 20ms at most.

Adam Schiavone
  • 2,412
  • 3
  • 32
  • 65
  • 2
    This code does *not* use additional threads. This code only ever involves one thread ever executing, the UI thread. – Servy Oct 25 '16 at 14:23
  • the details of what `async`, `await` & `Task.Delay` are actually doing here are a little loose... – Liam Oct 25 '16 at 14:25
  • 2
    [Stephen Clearys excellent blog covers async await well](http://blog.stephencleary.com/2012/02/async-and-await.html) and the details are also covered in [this question](http://stackoverflow.com/questions/14455293/how-and-when-to-use-async-and-await) – Liam Oct 25 '16 at 14:27
  • I struck that bit. @Liam thanks for the link, I'll read into it. Those operators have always been somewhat magic. – Adam Schiavone Oct 25 '16 at 14:28
  • 1
    In a nutshell `async`/`await` release the current UI thread while it's waiting for a long running external "process", they then continue as though nothing has happened once the "process" returns. It's async programming not threading, many people get these confused. `Task`s spawn **new** threads – Liam Oct 25 '16 at 14:31