23.3 Executing an Asynchronous Task from a GUI App

This section demonstrates the benefits of executing compute-intensive tasks asynchronously in a GUI app.

23.3.1 Performing a Task Asynchronously

Figure 23.1 demonstrates executing an asynchronous task from a GUI app. Consider the GUI at the end of Fig. 23.1. In the GUI’s top half, you can enter an integer, then click Calculate to calculate that integer’s Fibonacci value (discussed momentarily) using a compute-intensive recursive implementation (Section 23.3.2). Starting with integers in the 40s (on our test computer), the recursive calculation can take seconds or even minutes to calculate. If this calculation were to be performed synchronously, the GUI would freeze for that amount of time and the user would not be able to interact with the app (as we’ll demonstrate in Fig. 23.2). We launch the calculation asynchronously and have it execute on a separate thread so the GUI remains responsive. To demonstrate this, in the GUI’s bottom half, you can click Next Number repeatedly to calculate the next Fibonacci number by simply adding the two previous numbers in the sequence. For the screen captures in Fig. 23.1, we used the top half of the GUI to calculate Fibonacci(45), which took over a minute on our test computer. While that calculation proceeded in a separate thread, we clicked Next Number repeatedly to demonstrate that we could still interact with the GUI and that the iterative Fibonacci calculation is much more efficient.

A Compute-Intensive Algorithm: Calculating Fibonacci Numbers Recursively

The powerful technique of recursion was introduced in Section 7.16. The examples in this section and in Sections 23.423.5 each perform a compute-intensive recursive Fibonacci calculation (defined in the Fibonacci method at lines 53–63). The Fibonacci series


0, 1, 1, 2, 3, 5, 8, 13, 21, …

begins with 0 and 1, and each subsequent Fibonacci number is the sum of the previous two Fibonacci numbers. The Fibonacci series can be defined recursively as follows:


Fibonacci(0) = 0
Fibonacci(1) = 1
Fibonacci(n) = Fibonacci(n – 1) + Fibonacci(n – 2)

Fig. 23.1 Performing a compute-intensive calculation from a GUI app.

Alternate View

   1     // Fig.  23.1: FibonacciForm.cs
   2     // Performing a compute-intensive calculation from a GUI app
   3     using System;
   4     using System.Threading.Tasks;
   5     using System.Windows.Forms;
   6
   7     namespace FibonacciTest
   8     {
   9        public partial class FibonacciForm : Form
  10        {
  11           private long n1 = 0; // initialize with first Fibonacci number 
  12           private long n2 = 1; // initialize with second Fibonacci number
  13           private int count = 1; // current Fibonacci number to display
  14
  15           public FibonacciForm()
  16           {
  17              InitializeComponent();
  18           }
  19
  20          // start an async Task to calculate specified Fibonacci number
  21          private async void calculateButton_Click(object sender, EventArgs e)
  22          {
  23             // retrieve user's input as an integer
  24             int number = int.Parse(inputTextBox.Text);
  25
  26             asyncResultLabel.Text = "Calculating...";
  27             
  28             // Task to perform Fibonacci calculation in separate thread
  29             Task<long> fibonacciTask = Task.Run(() => Fibonacci(number));
  30
  31             // wait for Task in separate thread to complete
  32             await fibonacciTask;
  33
  34             // display result after Task in separate thread completes
  35             asyncResultLabel.Text = fibonacciTask.Result.ToString();
  36          }
  37
  38          // calculate next Fibonacci number iteratively
  39          private void nextNumberButton_Click(object sender, EventArgs e)
  40          {
  41              // calculate the next Fibonacci number
  42              long temp = n1 + n2; // calculate next Fibonacci number
  43              n1 = n2; // store prior Fibonacci number in n1
  44              n2 = temp; // store new Fibonacci
  45              ++count;
  46               
  47              // display the next Fibonacci number
  48              displayLabel.Text = $"Fibonacci of {count}:";
  49              syncResultLabel.Text = n2.ToString();
  50         }
  51
  52           // recursive method Fibonacci; calculates nth Fibonacci number
  53           public long Fibonacci(long n)
  54          {
  55               if (n == 0|| n == 1)
  56               {
  57                   return n;
  58               }
  59               else
  60               {
  61                   return Fibonacci(n - 1) + Fibonacci(n - 2);
  62               }
  63          }
  64      }
  65   }

Exponential Complexity

A word of caution is in order about recursive methods like the one we use here to generate Fibonacci numbers. The number of recursive calls that are required to calculate the nth Fibonacci number is on the order of 2n. This rapidly gets out of hand as n gets larger. Calculating only the 20th Fibonacci number would require on the order of 220 or about a million calls, calculating the 30th Fibonacci number would require on the order of 230 or about a billion calls, and so on. This exponential complexity can humble even the world’s most powerful computers! Calculating just Fibonacci(47) recursively—even on today’s most recent desktop and notebook computers—can take minutes.

23.3.2 Method calculateButton_Click

The Calculate button’s event handler (lines 21–36) initiates the call to method Fibonacci in a separate thread and displays the results when the call completes. The method is declared async (line 21) to indicate to the compiler that the method will initiate an asynchronous task and await the results. In an async method, you write code that looks as if it executes sequentially, and the compiler handles the complicated issues of managing asynchronous execution. This makes your code easier to write, debug, modify and maintain, and reduces errors.

23.3.3 Task Method Run: Executing Asynchronously in a Separate Thread

Line 29 creates and starts a Task<TResult> (namespace System.Threading.Tasks), which promises to return a result of generic type TResult at some point in the future. Class Task is part of .NET’s Task Parallel Library (TPL) for parallel and asynchronous programming. The version of class Task’s static method Run used in line 29 receives a Func<TResult> delegate (delegates were introduced in Section 14.3.3) as an argument and executes a method in a separate thread. The delegate Func<TResult> represents any method that takes no arguments and returns a result of the type specified by the TResult type parameter. The return type of the method you pass to Run is used by the compiler as the type argument for Run’s Func delegate and for the Task that Run returns.

Method Fibonacci requires an argument, so line 29 passes the lambda expression

() => Fibonacci(number)

which takes no arguments—this labmda encapsulates the call to Fibonacci with the argument number (the value entered by the user). The lambda expression implicitly returns the Fibonacci call’s result (a long), so it meets the Func<TResult> delegate’s requirements. In this example, Task’s static method Run creates and returns a Task<long>. The compiler infers the type long from the return type of method Fibonacci. We could declare local variable fibonacciTask (line 29) with var—we explicitly used the type Task<long> for clarity, because Task method Run’s return type is not obvious from the call.

23.3.4 awaiting the Result

Next, line 32 awaits the result of the fibonacciTask that’s executing asynchronously. If the fibonacciTask is already complete, execution continues with line 35. Otherwise, control returns to calculateButton_Click’s caller (the GUI event-handling thread) until the result of the fibonacciTask is available. This allows the GUI to remain responsive while the Task executes. Once the Task completes, calculateButton_Click continues execution at line 35, which uses Task property Result to get the value returned by Fibonacci and display it on asyncResultLabel.

An async method can perform statements between those that launch an asynchronous Task and await the results. In such a case, the method continues executing those statements after launching the asynchronous Task until it reaches the await expression.

Lines 29 and 32 can be written more concisely as


long result = await Task.Run(() => Fibonacci(number));

In this case, the await operator unwraps and returns the Task’s result—the long returned by method Fibonacci. You can then use the long value directly without accessing the Task’s Result property.

23.3.5 Calculating the Next Fibonacci Value Synchronously

When you click Next Number, the event handler nextNumberButton_Click (lines 39–50) executes. Lines 42–45 add the previous two Fibonacci numbers stored in instance variables n1 and n2 to determine the next number in the sequence, update instance variables n1 and n2 to their new values and increment instance variable count. Then lines 48–49 update the GUI to display the Fibonacci number that was just calculated.

The code in the Next Number event handler is performed in the GUI thread of execution that processes user interactions with controls. Handling such short computations in this thread does not cause the GUI to become unresponsive. Because the longer Fibonacci computation is performed in a separate thread, it’s possible to get the next Fibonacci number while the recursive computation is still in progress.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset