Async, Await and the UI Problem
Written by Mike James   
Thursday, 25 February 2021
Article Index
Async, Await and the UI Problem
The flow of control
Advanced await

The async/await asynchronous programming facilities in .NET solve one of its longstanding problems - how to write an elegant application that uses the UI correctly. But to avoid problems you still need to understand both the problem and the solution.

This is comes from my book on C# - a work in progress.

Deep C#

 Buy Now From Amazon

DeepCsharp360

 Chapter List

  1. Why C#?

    I Strong Typing & Type Safety
  2. Strong Typing
       Extract 
    Why Strong Typing
  3. Value & Reference
  4.    Extract Value And Reference
  5. Structs & Classes
       Extract
    Structs & Classes 
  6. Inheritance
      
    Extract
    Inheritance
  7. Interfaces & Multiple Inheritance
      
    Extract Interface
  8. Controlling Inheritance
    II Casting & Generics
  9. Casting - The Escape From Strong Typing
      
    Extract Casting I ***NEW!
  10. Generics
  11. Advanced Generics
  12. Anonymous & Dynamic
    Typing
    III Functions
  13. Delegates
  14. Multicast Delegates
  15. Anonymous Methods, Lambdas & Closures
    IV Async
  16. Threading, Tasks & Locking
  17. The Invoke Pattern
  18. Async Await
  19. The Parallel For
    V Data - LINQ, XML & Regular Expressions
  20. The LINQ Principle
  21. XML
  22. LINQ To XML
  23. Regular Expressions
    VI Unsafe & Interop
  24. Interop
  25. COM
  26. Custom Attributes
  27. Bit Manipulation
  28. Advanced Structs
  29. Pointers 

Extra Material

 <ASIN:1871962714>

 <ASIN:B09FTLPTP9>

One of the biggest problems facing any Windows forms or WPF programmer is that you can't use the UI thread to do much work. If you do the result is an unresponsive application. The proper solution is to use a new thread to do all of the heavy computation and leave the UI thread free to get on with what it is supposed to do - deal with user events. However the computation thread usually has to provide evidence that it has done something and this means that it has to interact with the UI components.

As is well known, UI components aren't threadsafe - hence the rule that only the thread that created a component can access it. You can optionally turn this rule off and allow the worker thread to access the UI but this isn't a good idea. The correct way to to allow the worker thread to access the UI is to use the Invoke method to ask the UI Thread to run a delegate that does the update using data from provided by the worker thread.

This may be the correct way but it results in very messy code and given it is such a common requirement we really could do with a simpler way of implementing two or more threads working with the UI and this is where async and await come into the picture.

Hands-on example

Start a new WPF project and you next need to reference the new assembly.Next put a button and two TextBlocks on the form - the button will start the process off and the TextBlocks will record its progress.

First let's look at the problem that we are trying to solve.  The Button's click event handler calls a method that does a lot of work:

private void button1_Click(object sender,
 RoutedEventArgs e)
{
textBlock1.Text = "Click Started";
DoWork();
textBlock2.Text = "Click Finished";
}

You can see that the first two textBlocks are changed to show what is happening. For the purpose of this example DoWork can be simulated by a routine that just loops and so keeps its thread occupied. You get the same overall result if DoWork simply waits for an I/O operation to complete - the important point is that as written it keeps the attention of the UI thread until it is complete. That is:

void DoWork()
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(500);
}
}

keeps the UI thread busy for 5 seconds and to use Thread.Sleep you also have to add:

using System.Threading;

What do you think you see if you run this program?

If you aren't familiar with the way WPF works you might think that you see "Click Started" appear and then after 5 seconds "Click Finished".

What actually happens is that you see both messages appear after the 5 seconds are up during which time the UI if frozen.

The reason for this behaviour is simply that the UI is frozen from the moment the DoWork method is called and this is usually before the "Click Started" text has been rendered to the display. This is exactly the reason you don't want any intensive computation on the UI thread - in fact you really don't want any computation on the UI thread at all!

Async and await

Now we can look at how to implement this property using the new async and await.

The first thing is to know is that any method that you put async in front of is an asynchronous method, which means it can be started and stopped rather than just run from first to last instruction. We could create a new method and mark is as asynchronous but to keep the example as much like the synchronous case described above we can simply change the Click event handler into an asynchronous method:

private async void button1_Click(
object sender, RoutedEventArgs e)
{
textBlock1.Text = "Click Started";
 DoWork();
textBlock2.Text = "Click Finished";
}

If you do this the compiler will complain that you have an asynchronous method without any awaits and so it will run it as a synchronous method anyway.

To take advantage of the asynchronous nature of the new event handler we have to await the completion of the DoWork method. However if you write:

private async void button1_Click(
object sender, RoutedEventArgs e)
{
textBlock1.Text = "Click Started";
await DoWork();
textBlock2.Text = "Click Finished";
}

Then the compiler will complain that you can't await a method that returns a void. Any method that you await has to return a Task or a Task<T> object where T is the type it would normally return.

In this case as nothing is returned we can use return a Task object:

Task DoWork()

The next question is what Task object do we actually return?

Banner

<ASIN:0596159838>

<ASIN:1430225491>

<ASIN:0672331012>

<ASIN:0321718933>

<ASIN:0735627045>



Last Updated ( Thursday, 25 February 2021 )