|Async, Await and the UI Problem
|Written by Mike James
|Thursday, 25 February 2021
Page 1 of 3
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.
Buy Now From Amazon
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.
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,
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:
keeps the UI thread busy for 5 seconds and to use Thread.Sleep you also have to add:
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(
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(
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:
The next question is what Task object do we actually return?
|Last Updated ( Thursday, 25 February 2021 )