What are threads?
A thread executes code synchronously—a set of instructions processed by the CPU “first in first out”. This can include virtually any kind of code, be it updating of the GUI, processing calculations, or waiting for user input.
Windows Forms is arguably one of the most used aspects of .NET Framework. GUI programs typically rely on two layers:
- Processing stack, which includes any code or task executed in the background, and
- User interface, which is supposed to give the user a means for instructing the program to execute tasks, and receiving timely “updates” or indications of progress.
Multithreading is a programming paradigm, which has become popular during the past decade. In a nutshell, the CPU can switch between two or more thread contexts so two tasks (e.g. calculation, and GUI update) can be executed in parallel—or asynchronously.
For example, a program can make full use of a multi-core processor by instructing each core to process a separate task, or a chunk of the whole task. This results in improved performance.
In the case with Windows Forms, a separate thread can be used to process a task while allowing the main thread (GUI) to provide timely information about the progress, or to let the user continue their work during the lengthy task.
In the example below, we are going to use the BackgroundWorker component. Common uses for it include any possibly lengthy operations such as:
- downloading images or information from the Internet
- assembling a list of files from various directories
- computing CPU-heavy results
A good practice with GUI programs is to keep the user informed of the process and results of such tasks, or to let them continue using the program while the task is executed.
Assume we have a ProgressBar control to show the task’s progress, and a ListView in details mode to show the results.
Start by adding a handler to the DoWork event of the worker. This handler will contain any code executed in the separate thread—it will calculate the first 90 Fibonacci numbers, with a delay between every iteration to slow down the operation. This delay will be natural in other cases, such as waiting for information to be fetched, or for a more serious calculation to be processed.
When the RunWorkerAsync method is called, the worker creates a separate thread, in which all of its code is executed. Despite the valid calls, a quick test reveals that updating the progress bar or list view from this thread results in an exception—most cross-thread operations with Windows Forms controls are invalid.
A solution is to move the UI update code to a separate “proxy” subroutine: the InvokeRequired property and Invoke method are used in combination to enter the necessary context. A delegate declaration with an identical method signature has to be written so that the Common Language Runtine can execute the code in the UI context.
Tip: The worker.IsBusy property can be used to prevent multiple operations from being started. Also, the RunWorkerCompleted event handler is useful for showing. A similar “proxy” method should be used when updating the UI in this case, too.