1

The following code abstracts away my actual task, which is to go through each pixel in a bitmap.

myButtonCommand.execute(async () => {
   await Task.Run(() => {
       while(i++<1000000) {
           // do some work
           if(i % 1000 == 0) // only update on every 1000th iteration
           {
               ProgressChanged.Invoke(this, i); // fire update event
               Thread.Sleep(n);
           }
       }
   });
});

The lambda that is supplied to myButtomCommand.execute runs on the UI thread. But since Tasks aren't guaranteed to run on the UI thread doesn't that mean that calling Thread.Sleep(n) could possibly freeze the UI thread?

The underlying problem is that I need to update a progress bar and without doing a Thread.Sleep(n) the UI thread gets blocked while the Task is spinning.

Now, what I ended up doing was using Thread.Sleep(0) so it will only go to sleep if another thread needs to run. But I don't understand why using Thread.Sleep(n) is necessary in the first place since, at least in my tests, it was on a separate thread anyways.

So I'm confused about two things:

  1. Is it safe to call Thread.Sleep(n) from a Task off the UI thread?
  2. In my example, why is Thread.Sleep(n) necessary to get the UI to update given the Task ran on a separate thread?
user875234
  • 2,399
  • 7
  • 31
  • 49
  • So have a think about how many times a second you'd be calling `ProgressChanged.Invoke()` in that loop if you didn't have a sleep... `ProgressChanged.Invoke()` will invoke code running on the UI thread. The correct fix is probably to call `ProgressChanged.Invoke()` much less frequently. – Matthew Watson Jun 01 '18 at 12:13
  • @MatthewWatson, right, many times. I updated my answer to have it only call `ProgressChanged.Invoke()` on every 1000th iteration, which I forgot to mention is also necessary. ...In my "production code" it uses a Stopwatch instead of `i%1000=0` to make it update a maximum of 10 times a second. – user875234 Jun 01 '18 at 12:17
  • It should manage 10 times a second without any issues - unless the response to the progress changed event is doing too much work. – Matthew Watson Jun 01 '18 at 12:18
  • The response just updates the progress bar. But weirdly, without the Thread.Sleep(n) the UI gets clogged up. Here's the commit to the repository if you would like to see it with your own eyes. Just comment out the if and encode a long (string.length=a lot) message. https://github.com/smchughinfo/steganographyjr/commit/c66fa2b667d1a846ba29c920140220280a4baba4 – user875234 Jun 01 '18 at 12:23
  • To answer 2): Show the code that subscribes to `ProgressChanged`. – Tewr Jun 01 '18 at 12:28
  • @Tewr it's just `myClassInstance.ProgressChanged += /*normal event handler signature*/ { Device.BeginInvokeOnMainThread(() => { ExecutionProgress = e; }); }` where e, is actually a double between 0 and 1. – user875234 Jun 01 '18 at 12:31
  • This is another kind of bug, a *firehose bug*. The Invoke() call pummels the UI thread with work to do. It you do this too often then it will start burning 100% core, constantly having to service the Invoke calls. Lower priority work no longer gets done, like responding to user input and painting the UI. Looks like a "freeze", almost indistinguishable from a Thread.Sleep() call done on the UI thread. You only have to keep human eyes convinced that you do this often enough, that's very easy to do. – Hans Passant Jun 01 '18 at 13:45

2 Answers2

1

and without doing a Thread.Sleep(n) the UI thread gets blocked while the Task is spinning.

That means you were just calling too many Invokes here.
The GUI thread was not 'blocked', just overworked.

Another, and probably better approach

 //if(i % 1000 == 0) 
   if(i % 1000000 == 0) 
bommelding
  • 2,969
  • 9
  • 14
  • the `i%1000==0` is just pseudo-code. i'm using a Stopwatch so that it updates, at most, every tenth of a second but that was more detail than I thought should be included. anyways, I think your original answer might work. That is, use BeginInvoke. Apparantly Invoke runs on the same thread but BeginInvoke runs on a different thread (https://stackoverflow.com/questions/229554/whats-the-difference-between-invoke-and-begininvoke). I can't try it right now but will try to update this question when I find out. – user875234 Jun 01 '18 at 15:14
  • BeginIvoke maps to Post, Invoke to Send on your sync context. To wait or not to wait for an answer. – H H Jun 01 '18 at 17:04
  • Well it (BeginInvoke) didn't work. I'm done messing with it. I'm just going to leave it as is which has it doing `Thread.Sleep(0)`, at most, 10 times a second. Not bad but I hate not understanding what the heck is going on. – user875234 Jun 02 '18 at 01:49
1

Similar and I can assure you the UI does not freeze.
Look up progress on async.

await Task.Delay(500); may be all you need.

async Task<int> TaskDelayAsync(CancellationToken ct, IProgress<int> progress)
{ 
    int i = 0; 
    while (true)
    {               
        i++;
        //Debug.WriteLine(i);
        progress.Report(i);
        ct.ThrowIfCancellationRequested();
        await Task.Delay(500);
    }
    return i;
}
paparazzo
  • 44,497
  • 23
  • 105
  • 176