When you run any program, its tasks compete for processor time with the operating system, other programs and other activities that the operating system is running on your behalf. When you execute the next example, the time to perform the Fibonacci calculations can vary based on your computer’s processor speed, number of cores and what else is running on your computer. It’s like a drive to the supermarket—the time it takes can vary based on traffic conditions, weather, timing of traffic lights and other factors.
Figure 23.3 also uses the recursive Fibonacci
method, but the two initial calls to Fibonacci
execute in separate threads. The first two outputs show the results on a dual-core computer. Though execution times varied, the total time to perform both Fibonacci
calculations (in our tests) was typically significantly less than the total sequential-execution time in Fig. 23.2. Dividing the compute-intensive calculations into threads and running them on a dual-core system does not perform the calculations twice as fast, but they’ll typically run faster than when performed in sequence on one core. Though the total time was the compute time for the longer calculation, this is not always the case, as there’s overhead inherent in using threads to perform separate Task
s.
The last two outputs show that executing calculations in multiple threads on a single-core processor can actually take longer than simply performing them synchronously, due to the overhead of sharing one processor among the app’s threads, all the other apps executing on the computer and the chores the operating system was performing.
awaiting
Multiple Tasks with Task Method WhenAll
In method startButton_Click
, lines 23 and 29 use Task
method Run
to create and start Task
s that execute method StartFibonacci
(lines 50–66)—one to calculate Fibonacci(46)
and one to calculate Fibonacci(45)
. To show the total calculation time, the app must wait for both Task
s to complete before executing lines 34–46. You can wait for multiple Task
s to complete by await
ing the result of Task static
method WhenAll
(line 31), which returns a Task
that waits for all of WhenAll
’s argument Task
s to complete and places all the results in an array. In this app, the Task
’s Result
is a TimeData[]
, because both of WhenAll
’s argument Task
s execute methods that return TimeData
objects. Class TimeData
is defined as follows in this project’s TimeData.cs
file:
class TimeData
{
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
We use objects of this class to store the time just before and immediately after the call to Fibonacci
—we use these properties to perform time calculations. The TimeData
array can be used to iterate through the results of the await
ed Task
s. In this example, we have only two Task
s, so we interact with the task1
and task2
objects directly in the remainder of the event handler.
StartFibonacci
Method StartFibonacci
(lines 50–66) specifies the task to perform—in this case, to call Fibonacci
(line 57) to perform the recursive calculation, to time the calculation (lines 56 and 58), to display the calculation’s result (line 60) and to display the time the calculation took (lines 61–63). The method returns a TimeData
object that contains the time before and after each thread’s call to Fibonacci
.
GUI
from a Separate ThreadLines 55, 60 and 63 in StartFibonacci
call method AppendText
(lines 82–92) to append text to the outputTextBox
. GUI controls are designed to be manipulated only by the GUI thread—all GUI event handlers are invoked in the GUI thread automatically. Modifying GUI controls one from a non-GUI thread can corrupt the GUI, making it unreadable or unusable. When updating a control from a non-GUI thread, you must schedule that update to be performed by the GUI thread. To do so in Windows Form
s, check the inherited Form
property InvokeRequired
(line 84). If this property is true
, the code is executing in a non-GUI thread and must not update the GUI directly. Instead, you call the inherited Form
method Invoke
method (line 86), which receives as an argument a Delegate
representing the update to perform in the GUI thread. In this example, we pass a MethodInvoker
(namespace System.Windows.Forms
)—a Delegate
that invokes a method with no arguments and a void
return type. We initialize the MethodInvoker
with a lambda expression that calls AppendText
. Line 86 schedules this MethodInvoker
to execute in the GUI thread. When the method is called from the GUI thread, line 90 updates the outputTextBox
. Similar concepts also apply to WPF and Universal Windows Platform GUIs.
awaiting
One of Several Tasks with Task Method WhenAny
Similar to WhenAll
, class Task
also provides static
method WhenAny
, which enables you to wait for any one of several Task
s specified as arguments to complete. WhenAny
returns the Task
that completes first. One use of WhenAny
might be to initiate several Task
s that perform the same complex calculation on computers around the Internet, then wait for any one of those computers to send results back. This would allow you to take advantage of computing power that’s available to you to get the result as fast as possible. In this case, it’s up to you to decide whether to cancel the remaining Task
s or allow them to continue executing. For details on how to do this, see
https://msdn.microsoft.com/library/jj155758
Another use of WhenAny
might be to download several large files—one per Task
. In this case, you might want all the results eventually, but you’d like to start processing immediately the results from the first Task
that returns. You could then call WhenAny
again for the remaining Task
s that are still executing.