C#: Empower Multitasking With The Awesome Task Class

The C# Task class is the “modern” way to handle multitasking in C#. It is commonly called asynchronous programming, where each task typically runs on a ThreadPool thread behind the scenes. From a user’s standpoint, each of these Tasks effectively is running simultaneously, similarly to how multiple ThreadPool threads appear to run..

Implementation using Task class instances requires the use of the async/await constructs. The only peculiarity of using async in a method definition is that C# expects there to be an await call in the async method. Furthermore, any use of await is expected to be in an async method. The result of this is effectively that each method called in a “chain” of calls starting with an async usually requires subsequently called methods to also be async.

Below is a flowchart of a trivial example using a ThreadPool to manage 2 independent threads running “simultaneously”:

Flowchart

Some quick explanation about the above flowchart:

  • The main thread creates and starts two distinct tasks. Each task will immediately begin execution by grabbing a ThreadPool thread from the default ThreadPool
  • After creating and starting the 2 separate Tasks, the main thread will then wait for 4 seconds before continuing
  • Each Task object (task1 and task2) will run until completion. If things go correctly, the Status property of each Task will change to “RanToCompletion”
  • The main thread uses the “await” statement to instruct the program to wait for a Task to run until completed
  • Once both tasks are complete, and not before, the main program will then exit

Code Sample

Below is the sample of code showing a complete program demonstrating the use of Tasks in asynchronous programming as depicted in the above flowchart:

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MultitaskingTasks
{
	internal class MultitaskingTasksExample
	{
		public static async Task Main(string[] args)
		{
			Console.WriteLine($"Main program started on thread {Thread.CurrentThread.ManagedThreadId}");

            // Create and start a task using Task.Run; "()" at start means no parameters needed

            Task task1 = Task.Run(() =>
            {
                Console.WriteLine($"Prime lister thru 30 on thread: {Thread.CurrentThread.ManagedThreadId}");
                Console.WriteLine($"Task A: 1 is a prime number.\nTask A:  2 is a prime number");
				for (int idx = 3; idx < 30; idx += 2)
				{
					if(IsPrime(idx)) {
                        Console.WriteLine($"Task A: {idx} is a prime number.");
                    }
                    Thread.Sleep(500);		// pace ourselves at one second
				}
                Console.WriteLine("Task A completed.");
            });

            Task task2 = Task.Run(() =>
            {
                Console.WriteLine($"Get Google home page on thread: {Thread.CurrentThread.ManagedThreadId}");
				String strGoogle = FetchUrl("http://www.google.com");
                Console.WriteLine("Task B completed.");
            });

            int nWaitTime = 4000;
			Thread.Sleep(nWaitTime);		// wait 10 seconds
            Console.WriteLine($"Main program waited {nWaitTime/1000} seconds.");

			await task1;
			await task2;
			Console.WriteLine("All tasks completed; exiting program.");
			Environment.Exit(0);
		}

		/// <summary>
		/// Get # of characters in the HTML of the Google Home Page
		/// </summary>
		/// <remarks> ### Worker Thread 1 ### </remarks>
		/// <param name="state">passed value</param>
		private static string FetchUrl(String strUrl)
		{
			// Create a new WebRequest Object to the mentioned URL.
			// Set the 'Timeout' property in Milliseconds.
			// Accordingly, this request will throw a WebException if it reaches the timeout limit before it is able to fetch the resource.
			int nLength = 0;        // length of Google home page HMTL
			try
			{
				WebRequest myWebRequest = WebRequest.Create("http://www.google.com");
				myWebRequest.Timeout = 10000;
				WebResponse myWebResponse = myWebRequest.GetResponse();
				Stream streamRx = myWebResponse.GetResponseStream();
				Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
				StreamReader readStream = new StreamReader(streamRx, encode);
				String strGoogleHome = readStream.ReadToEnd();
				nLength = strGoogleHome.Length;
				Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} found Google home has {nLength} chars.");
				return (strGoogleHome);
			}
			catch(Exception ex)
			{
				Console.WriteLine($"HTML error: {ex.Message}");
				return ("");
			}
		}

		private static bool IsPrime(int n)
		{
			for(int i = 3; i <= Math.Sqrt(n); i += 2)
			{
				if(n % i == 0) {
					return(false);
				}
			}
			return(true);
		}
	}
}

Your main focus for this program should be the use of the “Task.Run” calls. Each one uses a Lambda expression with no arguments pointing to the body of the task to be run. Task.Run creates a Task (which is returned) AND immediately begins the execution of the designated body. The main thread can “block” waiting for a task to be complete by using the await statement. Accordingly, that will essentially halt the main thread until the launched task completes or an error occurs. Internally, during the await C# will monitor the status of the Task until completion is detected before moving on to the next statement.

The two tasks are relatively simple. The first will loop through integers up until 30 and display if the number is a prime. The second shows how to load a web page into a stream that can be read using the StreamReader object’s “ReadToEnd” method. This one loads Google’s home page and simply displays the number of bytes of HTML received.

Output

If you examine the output below you can observe how it appears that task1 (A) and task2 (B) appear to be running simultaneously, along with the main program. After the two await statements are complete, the main program will then exit.

Multitasking in C#