Managing Non-blocking Calls on the UI Thread with Async Await

When async await arrived on the scene, writing asynchronous code with C# became increasingly simpler. And as such, the time and complexity of creating responsive applications was reduced. In this article, creating a response application by managing asynchronous code on your UI thread is what we’ll be doing.

The Application

The application you can build along with this article will be a WPF one using Visual Studio; I’m using Visual Studio 2015. Do note, however, that the techniques used in this article can also apply to Universal Windows Applications and WinForms. And, the first technique we’ll be looking at will be async code with I/O work.

Before we jump into the code, however, you’ll need to create your WPF application and be using .NET 4 and above. I’d recommend the latest version of .NET if possible.

In my WPF application, in the mainwindow.cs, I have XAML that looks like this…

<Grid>

   <Grid.RowDefinitions>
      <RowDefinition Height="*"/>
      <RowDefinition Height="40"/>
   </Grid.RowDefinitions>

   <ProgressBar Height="20"
                IsIndeterminate="True"
                IsEnabled="True"
                VerticalAlignment="Center"
                HorizontalAlignment="Stretch"/>

   <StackPanel Grid.Row="1"
               Orientation="Horizontal">

      <Button Content="Do IO Work"
              Click="IOWork_Clicked"/>

      <Button Content="Do CPU Work"/>

   </StackPanel>

</Grid>

And, my mainwindow.xaml.cs looks something like…

public partial class MainWindow : Window
{
   public MainWindow()
   {
      InitializeComponent();
   }

   private void IOWork_Clicked(object sender, RoutedEventArgs e)
   {

   }
}

Now, a quick explanation. In the centre of the window, if you run the application, you’ll see a progress bar set to indeterminate mode. It will show a constantly moving bar until it is stopped. I’m going to use this moving progress bar to visibly show blocking calls and non-blocking calls. And to do this, you won’t need to do anything else to the progress bar because when the UI-Thread becomes blocked, the UI will lock up, and the bar will freeze.

The running program looks like this for me when I run the preceding code.

Block
Figure 1: The running program

Therefore, let’s simulate a blocking call using a method that calls Thread.Sleep(). My method looks like this…

void IOWork()
{
   Thread.Sleep(5000);
}

Call this method from your IOWork_Clicked event handler, run the application, and observer the results. Every time you click the ‘Do IO Work’ button, you’ll see that the UI freezes for five seconds, and then resumes normal operation. Let us say that this method might read a very large file, such as a video into memory. This application could be frozen for a considerable amount of time.

So, how do we get around this? The first step is to modify the IOWork_Clicked event handler with the async keyword, which will look like:

private async void IOWork_Clicked(object sender, RoutedEventArgs e)

We’ll also need to make use of the await keyword to make this event handler truly async, and also re-write the IOWork method to be awaitable. My re-written code is now…

private async void IOWork_Clicked(object sender,
   RoutedEventArgs e)
{
   await IOWorkAsync();
}

async Task IOWorkAsync()
{
   for (int i = 0; i < 5; i++)
   {
      Debug.WriteLine(i);
      await Task.Delay(1000);
   }
}

I’ve added a line to create some visible output in Visual Studio’s output window so you can see that this method is indeed executing. Because the event handler has been modified with the async keyword, it now can execute asynchronously in the UI thread along with any other work the UI Thread is doing, which is animating the indeterminate progress bar. The async void event handler also becomes truly async because we have used the await keyword. On reaching the await keyword, execution of the event handler is suspected until the work done by the Task IOWorkAsync() is completed. And in the Task IOWorkAsync(), we are using Task.Delay() to simulate a long running process.

If you run the previous piece of code and press the ‘Do IO Work’ button again, you should see the count appearing in the output window in Visual Studio, and also notice that the indeterminate progress bar no longer freezes.

This technique can be applied to any IO type work, and .NET supplies many classes with async methods for you to do such work with; HttpClient is a very commonly used one. But what about CPU intensive work? Let’s simulate some CPU intensive work with the following code. I’m going to use Thread.Sleep(), even though it isn’t intensive CPU work, because it helps us to see what a blocked thread may look like.

private void CPUWork_Clicked(object sender,
   RoutedEventArgs e)
{
   CPUWork();
}

void CPUWork()
{
   for (int i = 0; i < 5; i++)
   {
      Thread.Sleep(1000);
      Debug.WriteLine(i);
   }
}

As before, if you run this code, you’ll see the progress bar freezing while the work is completed. But, before we carry on and deal with this CPU bound work in an asynchronous fashion, consider this. In the IO example earlier, all of the work—even though it was asynchronous—was all being completed on one thread: the UI thread. With intensive CPU work, we’ll have a high chance of blocking the UI Thread simply because of the large amount of work being done on the thread. Therefore, we are still likely to see the UI freezing. So, how do we deal with this problem?

Using Task, we can very simply offload this work to the thread pool by making the following changes to the code.

private async void CPUWork_Clicked(object sender,
   RoutedEventArgs e)
{
   await Task.Run(() => CPUWork());
}
Note: FYI, Task.Run is available to .NET 4.5 and later. If you are using .NET 4 prior to 4.5, Task.Factory.StartNew(Action) may be more familiar to you.

Doing so, we are queuing the work to be run on the thread pool, and the async void event handler in which Task.Run was awaited will suspend execution until the work has completed. Please note that in the demo code, I’m using Debug.WriteLine to output some visible evidence the code is being executed. The WriteLine method is a static method and therefore is thread safe. Using Task.Run offloads the work to another thread or threads in some cases, which is something to keep in mind when dealing with objects marshalled for another thread.

I’m going to leave this with your good self; but before I close, I’d like to say that there is quite a lot more to this story than what’s been discussed in this article. This is only a high level, quick overview of managing blocking calls in your .NET applications. If you have any questions on any of the content in this article, you can find me on Twitter @GLanata.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read