In this section, you will use threads to improve multitasking in a graphical app.
Visual Studio Code cannot be used to create GUI applications, so you will need Visual Studio 2017 on Windows 7 or later to complete this last section of the chapter. In Chapter 13, Building Universal Windows Platform Apps Using XAML, you will learn about Universal Windows Platform apps; however, these can only be created on Windows 10. So, if you only have Visual Studio Code on a non-Windows OS, at this point, you might want to jump ahead to Chapter 14, Building Web Applications Using ASP.NET Core MVC.
C# 5 introduced two keywords to simplify working with the Task
type. They are especially useful for:
In this chapter, we will explore how the async
and await
keywords can implement multitasking with a GUI running on Windows 7 or later.
In Chapter 14, Building Web Applications Using ASP.NET Core MVC, we will explore how the async
and await
keywords can improve scalability in web applications.
In Chapter 15, Building Mobile Apps Using Xamarin.Forms and ASP.NET Core Web API, we will explore how the async
and await
keywords can implement multitasking with a mobile GUI and improve scalability in a web service.
In Visual Studio 2017, go to File | Add | New Project.... In the Add
New Project dialog, in the Installed | Templates list, select Visual C#. In the center list, select WPF App (.NET Framework), type the name as Ch12_GUITasks
, and then click on OK.
You will learn more about XAML in the next chapter, but for now, just enter the following markup in the XAML view inside the <Grid>
element:
<StackPanel> <Button Name="GetProductsButton">Get Products</Button> <TextBox>Type in here while the products load...</TextBox> <ListBox Name="ProductsListBox"></ListBox> </StackPanel>
Your main editor window should now look like the following screenshot:
Inside the Button
element, after setting the Name
, enter an attribute named Click
, as shown in the following screenshot, and when the IntelliSense appears, press Enter to insert a new event handler:
Choose the View | Code menu, or press
F7, or click on the MainWindow.xaml.cs
tab.
Add the following code to the top of the code file:
using System.Data.SqlClient;
Add the following code inside the GetProductsButton_Click
method:
var connection = new SqlConnection( @"Data Source=(localdb)mssqllocaldb;" + "Initial Catalog=Northwind;Integrated Security=true;"); connection.Open(); var getProducts = new SqlCommand( "WAITFOR DELAY '00:00:05';" + "SELECT ProductID, ProductName, UnitPrice FROM Products", connection); SqlDataReader reader = getProducts.ExecuteReader(); int indexOfID = reader.GetOrdinal("ProductID"); int indexOfName = reader.GetOrdinal("ProductName"); int indexOfPrice = reader.GetOrdinal("UnitPrice"); while (reader.Read()) { ProductsListBox.Items.Add( string.Format("{0}: {1} costs {2:c}", reader.GetInt32(indexOfID), reader.GetString(indexOfName), reader.GetDecimal(indexOfPrice))); } reader.Dispose(); connection.Dispose();
Run the WPF app by pressing Ctrl + F5.
Click inside the text box and enter some text. The user interface is responsive.
Click on the Get Products button and then try to enter some text in the text box again.
The user interface is blocked because the thread is busy running the SQL command, as shown in the following screenshot. Only once the list of products has been populated does the UI become responsive and you can type into the text box again.
The types in the SqlClient
namespace have been improved in .NET Framework 4.5 and later by giving any method that might take a long time an asynchronous equivalent that returns a Task
.
For example, the SqlConnection
class has both an Open
method that returns void
and an OpenAsync
method that returns Task
. SqlCommand
has both an ExecuteReader
method that returns SqlDataReader
and an ExecuteReaderAsync
method that returns Task<SqlDataReader>
.
We can use these Task
objects as we did earlier, but that would still block the user interface when we call any of the Wait
methods.
Instead, we can use the await
keyword for any Task
. This means that the main thread will not be blocked while we wait, but will remember its current position within the statements so that, once the Task
has completed, the main thread continues executing from that same point. This allows us to write code that looks as simple as synchronous, but underneath, it is much more complex.
Modify the statements as shown in the following code. Note that, to use the await
keyword, we must mark the containing method await
with the async
keyword. They always work as a pair:
private async void GetProductsButton_Click( object sender, RoutedEventArgs e) { var connection = new SqlConnection( @"Data Source=(localdb)mssqllocaldb;" + "Initial Catalog=Northwind;Integrated Security=true;"); await connection.OpenAsync(); var getProducts = new SqlCommand( "WAITFOR DELAY '00:00:05';" + "SELECT ProductID, ProductName, UnitPrice FROM Products", connection); SqlDataReader reader = await getProducts.ExecuteReaderAsync(); int indexOfID = reader.GetOrdinal("ProductID"); int indexOfName = reader.GetOrdinal("ProductName"); int indexOfPrice = reader.GetOrdinal("UnitPrice"); while (await reader.ReadAsync()) { ProductsListBox.Items.Add( string.Format("{0}: {1} costs {2:c}", await reader.GetFieldValueAsync<int>(indexOfID), await reader.GetFieldValueAsync<string>(indexOfName), await reader.GetFieldValueAsync<decimal>(indexOfPrice))); } reader.Dispose(); connection.Dispose(); }
Rerun the application by pressing Ctrl + F5.
This time, after clicking on the Get Products button, you will be able to enter text in the text box while the command executes:
Here are some of the other types that have an asynchronous method support:
Type |
Methods |
|
|
|
|
|
|
In C# 5, it was only possible to use the await
keyword in a try
exception handling block, but not in a catch
block.
In C# 6 or later, it is now possible to use await
in both try
and catch
blocks.
In the previous example, we saw how using the async
and await
keywords can improve the performance of a client-side graphical application by preventing the blocking of the user interface thread.
The same keywords can be applied on the server side when building web applications and services. From the client application's point of view, nothing changes (or they might even notice a small increase in the time for a request to return). So, from a single client's point of view, the use of async
on the server side makes their experience worse!
On the server side, additional, cheaper worker threads are created to wait for long-running tasks to finish so that expensive IO threads can handle other client's requests instead of being blocked. This improves the overall scalability of a web application or service. More clients can be supported simultaneously.
You will create asynchronous operations on the server side in Chapter 14, Building Web Applications Using ASP.NET Core MVC.
18.226.104.27