Running tasks asynchronously

First, we will write a simple console application that needs to execute three methods, and execute them synchronously (one after the other).

Running multiple actions synchronously

In Visual Studio 2017, press Ctrl + Shift + N or go to File | New | Project....

In the New Project dialog, in the Installed | Templates list, expand Visual C#, and select .NET Core. In the center list, select Console App (.NET Core), type the name as Ch12_Tasks, change the location to C:Code, type the solution name as Chapter12, and then click on OK.

In Visual Studio Code, create a directory named Chapter12 with a subfolder named Ch12_Tasks, and open the Ch12_Tasks folder. In the Integrated Terminal, execute the command: dotnet new console.

In both Visual Studio 2017 and Visual Studio Code, ensure that the following namespaces have been imported:

    using System; 
    using System.Threading; 
    using System.Threading.Tasks; 
    using System.Diagnostics; 
    using static System.Console; 

There will be three methods that need to be executed: the first takes three seconds, the second takes two seconds, and the third takes one second. To simulate that work, we can use the Thread class to tell the current thread to go to sleep for a specified number of milliseconds.

Inside the Program class, add the following code:

    static void MethodA() 
    { 
      WriteLine("Starting Method A..."); 
      Thread.Sleep(3000); // simulate three seconds of work 
      WriteLine("Finished Method A."); 
    } 
 
    static void MethodB() 
    { 
      WriteLine("Starting Method B..."); 
      Thread.Sleep(2000); // simulate two seconds of work 
      WriteLine("Finished Method B."); 
    } 
 
    static void MethodC() 
    { 
      WriteLine("Starting Method C..."); 
      Thread.Sleep(1000); // simulate one second of work 
      WriteLine("Finished Method C."); 
    } 

In the Main method, add the following statements:

    static void Main(string[] args) 
    { 
      var timer = Stopwatch.StartNew(); 
      WriteLine("Running methods synchronously on one thread."); 
      MethodA(); 
      MethodB(); 
      MethodC(); 
      WriteLine($"{timer.ElapsedMilliseconds:#,##0}ms elapsed."); 
      WriteLine("Press ENTER to end."); 
      ReadLine(); 
    } 

Run the console application and view the output.

Note

In Visual Studio 2017, press Ctrl + F5. In Visual Studio Code, in Integrated Terminal, enter the command: dotnet run

As there is only one thread, the total time required is just over six seconds:

Running methods synchronously on one thread.
Starting Method A...
Finished Method A.
Starting Method B...
Finished Method B.
Starting Method C...
Finished Method C.
6,047ms elapsed.
Press ENTER to end.

Running multiple actions asynchronously using tasks

The Thread class has been available since the first version of C# and can be used to create new threads and manage them, but it can be tricky to work with directly.

C# 4 introduced the Task class, which is a wrapper around a thread that enables easier creating and management. Creating multiple threads wrapped in tasks will allow our code to execute asynchronously (at the same time).

We will look at three ways to start the methods using Task instances. Each has a slightly different syntax, but they all define a Task and start it.

Comment out the calls to the three methods and the associated console message, and then add the new statements, as shown highlighted in the following code:

    static void Main(string[] args) 
    { 
      var timer = Stopwatch.StartNew(); 
      //WriteLine("Running methods synchronously on one thread."); 
      //MethodA(); 
      //MethodB(); 
      //MethodC(); 
      WriteLine("Running methods asynchronously on multiple threads."); 
      Task taskA = new Task(MethodA); 
      taskA.Start(); 
      Task taskB = Task.Factory.StartNew(MethodB); 
      Task taskC = Task.Run(new Action(MethodC)); 
      WriteLine($"{timer.ElapsedMilliseconds:#,##0}ms elapsed."); 
      WriteLine("Press ENTER to end."); 
      ReadLine(); 
    } 

Rerun the console application and view the output.

The actual elapsed milliseconds will depend on the performance of your CPU, so you are likely to see a different value than shown in the following example output:

Running methods asynchronously on multiple threads.
10 milliseconds elapsed.
Press ENTER to end.
Starting Method C...
Starting Method A...
Starting Method B...
Finished Method C.
Finished Method B.
Finished Method A.

Note the elapsed time is output almost immediately, because each of the three methods are now being executed by three new threads. The original thread continues executing until it reaches the ReadLine call at the end of the Main method.

Meanwhile, the three new threads execute their code simultaneously, and they start in any order. MethodC will usually finish first, because it takes only one second, then MethodB, and finally MethodA, because it takes three seconds.

However, the actual CPU used has a big effect on the results. It is the CPU that allocates time slices to each process to allow them to execute their threads. You have no control over when the methods run.

Waiting for tasks

Sometimes, you need to wait for a task to complete before continuing. To do this, you can use the Wait method on a Task instance, or the WaitAll or WaitAny static methods on an array of tasks.

Method

Description

t.Wait()

Waits for the task instance named t to complete execution.

Task.WaitAny(Task[])

Waits for any of the tasks in the array to complete execution.

Task.WaitAll(Task[])

Waits for all the tasks in the array to complete execution.

Add the following statements to the Main method immediately after creating the three tasks. This will combine references to the three tasks into an array and pass them to the WaitAll method. Now, the original thread will pause on that statement, waiting for all three tasks to finish before outputting the elapsed time:

    Task[] tasks = { taskA, taskB, taskC }; 
    Task.WaitAll(tasks); 

Rerun the console application and view the output:

Running methods asynchronously on multiple threads.
Starting Method B...
Starting Method C...
Starting Method A...
Finished Method C.
Finished Method B.
Finished Method A.
3,024 milliseconds elapsed.
Press ENTER to end.

Notice that the total time is now slightly more than the time to run the longest method. If all three tasks can be performed at the same time, then this will be all we need to do.

However, often a task is dependent on the output from another task. To handle this scenario, we need to define continuation tasks.

Continuing with another task

Add the following methods to the Program class:

    static decimal CallWebService() 
    { 
      WriteLine("Starting call to web service..."); 
      Thread.Sleep((new Random()).Next(2000, 4000)); 
      WriteLine("Finished call to web service."); 
      return 89.99M; 
    } 
 
    static string CallStoredProcedure(decimal amount) 
    { 
      WriteLine("Starting call to stored procedure..."); 
      Thread.Sleep((new Random()).Next(2000, 4000)); 
      WriteLine("Finished call to stored procedure."); 
      return $"12 products cost more than {amount:C}."; 
    } 

These methods simulate a call to a web service that returns a monetary amount that then needs to be used to retrieve how many products cost more than that amount in a database. The result returned from the first method needs to be fed into the input of the second method.

Note

I used the Random class to wait for a random interval of between two and four seconds for each method call to simulate the work.

Inside the Main method, comment out the previous tasks by highlighting the statements and, in Visual Studio Code, press Cmd + K, C, or in Visual Studio 2017 press Ctrl + K, C.

Then, add the following statements before the existing statement that outputs the total time elapsed and then calls ReadLine to wait for the user to press Enter:

    WriteLine("Passing the result of one task as an input into
    another."); 
 
    var taskCallWebServiceAndThenStoredProcedure =  
      Task.Factory.StartNew(CallWebService) 
      .ContinueWith(previousTask => 
        CallStoredProcedure(previousTask.Result)); 
 
    WriteLine($"{taskCallWebServiceAndThenStoredProcedure.Result}"); 

Run the console application and view the output:

Passing the result of one task as an input into another.
Starting call to web service...
Finished call to web service.
Starting call to stored procedure...
Finished call to stored procedure.
12 products cost more than £89.99.
5,971 milliseconds elapsed.
Press ENTER to end.

Nested and child tasks

Add a new console application project named Ch12_NestedAndChildTasks.

In Visual Studio 2017, in the solution's Properties, remember to change Startup Project to Current selection.

Ensure the following namespaces have been imported:

    using System; 
    using System.Threading; 
    using System.Threading.Tasks; 
    using System.Diagnostics; 
    using static System.Console; 

Inside the Main method, add the following statements:

    var outer = Task.Factory.StartNew(() => 
    { 
      WriteLine("Outer task starting..."); 
      var inner = Task.Factory.StartNew(() => 
      { 
        WriteLine("Inner task starting..."); 
        Thread.Sleep(2000); 
        WriteLine("Inner task finished."); 
      }); 
    }); 
    outer.Wait(); 
    WriteLine("Outer task finished."); 
    WriteLine("Press ENTER to end."); 
    ReadLine(); 

Run the console application and view the output:

Outer task starting...
Outer task finished.
Inner task starting...
Inner task finished.
Press ENTER to end.

Note that, although we wait for the outer task to finish, its inner task does not have to finish as well. To link the two tasks, we must use a special option.

Modify the existing code that defines the inner task to add a TaskCreationOption value of AttachedToParent:

    var inner = Task.Factory.StartNew(() => 
    { 
      WriteLine("Inner task starting..."); 
      Thread.Sleep(2000); 
      WriteLine("Inner task finished."); 
    }, TaskCreationOptions.AttachedToParent); 

Rerun the console application and view the output. Note that the inner task must finish before the outer task can:

Outer task starting...
Inner task starting...
Inner task finished.
Outer task finished.
Press ENTER to end.
..................Content has been hidden....................

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