0

This is my event handler code:

protected async void TestrunSaveExecute()
{
    bool saveResult = await SaveTestRunAsync();
}

In order to keep the UI responsive, I used the async/await method.

In my understanding, I can now do some lengthy operations in SaveTestRunAsync() without blocking the UI, since it is decoupled by using the await keyword.

private async Task<bool> SaveTestRunAsync()
{
    //System.Threading.Thread.Sleep(5000); --> this blocks the UI
    await Task.Delay(5000); // this doesn't block UI

    return true;
}

Could you please explain why the call to Thread.Sleep still blocks the UI, and Task.Delay doesn't?

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
Ronald McBean
  • 1,417
  • 2
  • 14
  • 27

4 Answers4

14

The code is still running on the UI thread.

It is not running on a background thread.

As such, any lengthy, costly, operation you're doing in that async method will still block the UI for that period of time.

Thread.Sleep puts the UI thread to sleep.

You need to understand how async and await works in this case.

await here basically says this:

Let's split the method in two here. The first portion is whatever executes up to the point of the await. The second portion is whatever should execute after the awaitable object has completed.

So, basically, the method executes up until it reaches await Task.Delay(5000);. Then it puts a "delay of 5 seconds" into play, and says "schedule the rest of the code to execute when that is done". Then it returns so that the UI thread can keep pumping messages.

Once the 5 seconds is up, the UI thread executes the rest of that method.

Basically, this is good if you do asynchronous I/O, but not so good if you do costly operations, like processing large datasets or similar.

So how then can you do that?

You can use Task.Run.

This will spin up another thread to execute the delegate it is given, and the UI thread is free to do other stuff in the meantime.

Basically you can think of a method using await as this:

Life of method:    <------------------------------------------------------->
Parts:             [ start of method ----][awaitable][ rest of method -----]

So the method will execute the first portion, until it reaches await X, then it will check if X is already done, if it's not done, it will set up some Task objects in such a way that the awaitable object gets to run, and once it has completed, the "rest of the method" gets to run.

If X was already done, perhaps it's an asynchronous I/O operation that has already completed, or it completed really fast, then the method will keep executing the rest of the method as though you hadn't written await there.

But if not, then it returns. This is important, because it lets the UI thread (in this case) get back to pumping messages like mouse clicks and things from the user.

Once the task waiting for that "awaitable object" is told that the awaitable is completed, the "rest of the method" is scheduled, which basically (in this case) puts a message into the message queue asking it to execute the rest of that method.

The more await statements you have in such a method, the more pieces, basically it will just split the method up into more parts.

You can do all of this with the Task class that was introduced in an earlier .NET version. The whole purpose of async / await is to make it easier to write code, since wrapping up your code in task objects had the unfortunate effect of turning your code inside out, and making it hard to handle things like exceptions and loops.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • 2
    +1 for explaining it in a way even a beginner can understand it. – Nzall Aug 01 '14 at 10:58
  • Hi @Lasse, thanks for the excellent explanation. So my understanding is code till await would be executed in the same thread. When it reaches there and the task is not complete, it gets scheduled on another thread by Task. Then when the other thread indicates the task has completed, the original thread executes the rest of the method after await (please correct if wrong). However I am wondering how the original thread picks up the remaining method. After the control returned to original method, it might have moved on to other code it is executing (and not necessarily awaiting on async method). – r_honey Aug 15 '14 at 19:53
  • @Lasse suppose an async method takes 10 secs to complete. So after await, controls returns to calling thread. Now, what if my calling thread is ASP.NET and I choose not to await on async method, instead I allow my Http request to complete. The thread itself goes back to ASP.NET thread pool and when async task completes after 10 secs, thread might either be in pool waiting or assigned to another request. Would it still be chosen to process the remainder of the async method. Second, what if it is already executing another piece of code.Lastly, what if the thread is dead altogether(custom thread) – r_honey Aug 15 '14 at 19:59
2

In my understanding, I can now do some lengthy operations in SaveTestRunAsync() without blocking the UI, since it is decoupled by using the await keyword.

async/await doesn't mean you can do "lengthy operations" without blocking the UI. On the contrary, when you use await, you aren't internally queueing up work on a different thread, your code continues to execute on the same thread (in your case its the UI thread), until hitting the inner await and returning to the caller.

The difference between Thread.Sleep and Task.Delay is that the former is a blocking call, it will halt your UI thread until the time specified is over. The latter, will internally use a Timer and yield control back to the calling method. Once that timer's clock elapses, it will resume execution where it left off (this is where compiler magic comes in)

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
1

Await does not create a thread or a task, it "simply" calls the awaited function (which is not supposed to block, otherwise the caller will be blocked as this is still synchronous), asks the compiler to generate a continuation for the task returned by the awaited function, containing the "rest" of the code after the await, and returns a new task to the caller (if the awaiting function returns a task). That task will be signalled as complete when the continuation completes.

The framework makes sure the continuation will run on the UI thread (if the application has one), which is a must if you want to be able to access UI elements from the asynchonous functions.

So an asynchronous function should not contain a blocking call. Blocking calls should be "wrapped" in a task using Task.Run()

Thierry
  • 342
  • 2
  • 6
0

Async/await is a higher level abstraction than threads.

The actual implementation may use a worker thread to implement async, but it may (and often does) use other synchronization techniques within same thread.

So a call to Thread.Sleep will block the UI thread, if the current implementation of async/await is not thread-based.

Related reading: It's All About the SynchronizationContext .

GSerg
  • 76,472
  • 17
  • 159
  • 346