The task-based asynchronous pattern

The task-based asynchronous pattern (TAP) is a pattern that's used to represent arbitrary asynchronous operations. The concept of this pattern is to represent asynchronous operations in a method and combine the status of the operation and the API that is used to interact with these operators for them to become a single object. The objects are the Task and Task<TResult> types in the System.Threading.Tasks namespace.

Introducing the Task and Task<TResult> classes

The Task and Task<TResult> classes were announced in .NET Framework 4.0 in order to represent an asynchronous operation. It uses threads that are stored in the thread pool but offers the flexibility of how the task is created. We use the Task class when we need to run a method as a task but don't need the return value; otherwise, we use the Task<TResult> class when we need to get the return value.

Note

We can find a complete reference, including methods and properties, inside Task and Task<TResult> on the MSDN site at https://msdn.microsoft.com/en-us/library/dd321424(v=vs.110).aspx.

Applying a simple TAP model

Let's start our discussion on TAP by creating the following code, which we can find in the TAP.csproj project, and use it to read a file asynchronously:

public partial class Program 
{ 
  public static void ReadFileTask() 
  { 
    bool IsFinish = false; 
    FileStream fs = File.OpenRead( 
      @"......LoremIpsum.txt"); 
    byte[] readBuffer = new byte[fs.Length]; 
    fs.ReadAsync(readBuffer,  0,  (int)fs.Length) 
      .ContinueWith(task => { 
      if (task.Status ==  
        TaskStatus.RanToCompletion) 
        { 
          IsFinish = true; 
          Console.WriteLine( 
          "Read {0} bytes.", 
          task.Result); 
        } 
        fs.Dispose();}); 
    //do other work while file is read 
    int i = 0; 
    do 
    { 
      Console.WriteLine("Timer Counter: {0}", ++i); 
    } 
    while (!IsFinish); 
    Console.WriteLine("End of ReadFileTask() method"); 
  } 
} 

As we can see in the preceding code, the ReadAsync() method inside the FileStream class will return Task<int>, which in this case will indicate the number of bytes that have been read from the file. After invoking the ReadAsync() method, we invoke the ContinueWith() extension method using method chaining, as discussed in Chapter 1, Tasting Functional Type in C# . It allows us to specify Action<Task<T>>, which will be run after the asynchronous operation is completed.

By invoking the ContinueWith() method after the task is completed, the delegate will be run in a synchronous operation immediately. And if we run the preceding ReadFileTask() method, we get the following output on the console:

Applying a simple TAP model

Using the WhenAll() extension method

We successfully applied a simple TAP in the previous section. Now, we will continue by asynchronously reading two files and then processing the other operation only when both the reading operations have been completed. Let's take a look at the following code, which will demonstrate our need:

public partial class Program 
{ 
  public static void ReadTwoFileTask() 
  { 
    bool IsFinish = false; 
    Task readFile1 = 
      ReadFileAsync( 
      @"......LoremIpsum.txt"); 
    Task readFile2 = 
      ReadFileAsync( 
      @"......LoremIpsum2.txt"); 
    Task.WhenAll(readFile1, readFile2) 
      .ContinueWith(task => 
      { 
        IsFinish = true; 
        Console.WriteLine( 
        "All files have been read successfully."); 
      }); 
      //do other work while file is read 
      int i = 0; 
      do 
      { 
        Console.WriteLine("Timer Counter: {0}", ++i); 
      } 
      while (!IsFinish); 
      Console.WriteLine("End of ReadTwoFileTask() method"); 
    } 
    public static Task<int> ReadFileAsync(string filePath) 
    { 
      FileStream fs = File.OpenRead(filePath); 
      byte[] readBuffer = new byte[fs.Length]; 
      Task<int> readTask = 
        fs.ReadAsync( 
        readBuffer, 
        0, 
        (int)fs.Length); 
      readTask.ContinueWith(task => 
      { 
        if (task.Status == TaskStatus.RanToCompletion) 
        Console.WriteLine( 
          "Read {0} bytes from file {1}", 
          task.Result, 
          filePath); 
        fs.Dispose(); 
      }); 
      return readTask; 
    } 
} 

As we can see, we use the Task.WhenAll() method to wrap the two tasks that are passed in as parameters into a larger asynchronous operation. It then returns a task that represents the combination of these two asynchronous operations. We don't need to wait for the completeness of both files' reading operations but it adds a continuation for when these two files have been read successfully.

If we run the preceding ReadTwoFileTask() method, we get the following output on the console:

Using the WhenAll() extension method

As we have discussed earlier that the drawback of APM pattern is that we cannot cancel the background process, now let's try to cancel the list of tasks in TAP by refactoring the preceding code we have. The complete code will become like the following:

public partial class Program 
{ 
  public static void ReadTwoFileTaskWithCancellation() 
  { 
    bool IsFinish = false; 
 
    // Define the cancellation token. 
    CancellationTokenSource source = 
      new CancellationTokenSource(); 
    CancellationToken token = source.Token; 
 
    Task readFile1 = 
      ReadFileAsync( 
      @"......LoremIpsum.txt"); 
    Task readFile2 = 
      ReadFileAsync( 
      @"......LoremIpsum2.txt"); 
 
    Task.WhenAll(readFile1, readFile2) 
      .ContinueWith(task => 
      { 
        IsFinish = true; 
        Console.WriteLine( 
          "All files have been read successfully."); 
      } 
      , token 
    ); 
 
    //do other work while file is read 
    int i = 0; 
    do 
    { 
      Console.WriteLine("Timer Counter: {0}", ++i); 
      if (i > 10) 
      { 
        source.Cancel(); 
        Console.WriteLine( 
          "All tasks are cancelled at i = " + i); 
         break; 
       } 
     } 
     while (!IsFinish); 
 
     Console.WriteLine( 
       "End of ReadTwoFileTaskWithCancellation() method"); 
    } 
} 

As we can see from the preceding code, we add CancellationTokenSource and CancellationToken to inform the cancellation process. We then pass token to the Task.WhenAll() function. After the tasks have run, we can cancel the tasks using the source.Cancel() method.

The following is the output we will get on the console if we run the preceding code:

Using the WhenAll() extension method

The preceding output tells us that the tasks have been canceled successfully in the 11th counter because the counter has been higher than 10.

Wrapping an APM into a TAP model

If the framework doesn't offer a TAP model for asynchronous operation, we can, if we want, wrap APM BeginXx and EndXx methods into the TAP model using the Task.FromAsync method. Let's take a look at the following code in order to demonstrate the wrapping process:

public partial class Program 
{ 
  public static bool IsFinish; 
  public static void WrapApmIntoTap() 
  { 
    IsFinish = false; 
    ReadFileAsync( 
      @"......LoremIpsum.txt"); 
      //do other work while file is read 
      int i = 0; 
    do 
    { 
      Console.WriteLine("Timer Counter: {0}", ++i); 
    } 
    while (!IsFinish); 
    Console.WriteLine( 
      "End of WrapApmIntoTap() method"); 
  } 
  private static Task<int> ReadFileAsync(string filePath) 
  { 
    FileStream fs = File.OpenRead(filePath); 
    byte[] readBuffer = new Byte[fs.Length]; 
    Task<int> readTask = 
      Task.Factory.FromAsync( 
      (Func<byte[], 
      int, 
      int, 
      AsyncCallback, 
      object, 
      IAsyncResult>) 
    fs.BeginRead, 
    (Func<IAsyncResult, int>) 
    fs.EndRead, 
    readBuffer, 
    0, 
    (int)fs.Length, 
    null); 
    readTask.ContinueWith(task => 
    { 
      if (task.Status == TaskStatus.RanToCompletion) 
      { 
        IsFinish = true; 
        Console.WriteLine( 
          "Read {0} bytes from file {1}", 
          task.Result, 
          filePath); 
      } 
      fs.Dispose(); 
    }); 
    return readTask; 
  } 
} 

From the preceding code, we can see that we use the BeginRead() and EndRead() methods, which are actually APM patterns, but we use them in the TAP model, as shown in the following code snippet:

Task<int> readTask = 
  Task.Factory.FromAsync( 
    (Func<byte[], 
    int, 
    int, 
    AsyncCallback, 
    object, 
    IAsyncResult>) 
    fs.BeginRead, 
    (Func<IAsyncResult, int>) 
    fs.EndRead, 
    readBuffer, 
    0, 
    (int)fs.Length, 
  null); 

And if we run the preceding WrapApmIntoTap() method, we will get the following output on the console:

Wrapping an APM into a TAP model

As we can see in the screenshot of the output result, we have successfully read the LoremIpsum.txt file using the BeginRead() and EndRead() methods wrapped into the TAP model.

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

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