Asynchrony - Expert C# 5.0: with .NET 4.5 Framework (2013)

Expert C# 5.0: with .NET 4.5 Framework (2013)

CHAPTER 14

images

Asynchrony

A powerful new feature has been added for asynchronous programming in C# language 5.0. This new feature comes in the form of two new keywords—async and await—which are used to make the synchronous method asynchronous. When you use these two keyword in your method, the C# compiler performs a transformation of your method code to generate the appropriate code, such as stub method and state machine, to do the asynchronous operation behind the scenes. This new feature makes asynchronous programming much easier than the exiting asynchronous pattern used in .NET.

This chapter will define the basics of synchronous and asynchronous programming and then explain asynchronous programming using the Task class introduced in .NET Framework 4.0. It will explain how to add a continuation block in the Task class and use different options such asTaskStatus and TaskContinuationOptions. Exception handling in the Task class is discussed to show how this has been made easier. The chapter will also explain in detail async and await, how to use them, and how the C# compiler takes care of these two keywords to implement the necessary code behind the scenes. Finally, the chapter will show how to use Task-based asynchronous patterns using the async and await keywords.

Synchronous and Asynchronous Programming

A synchronous operation does not return to its caller until it finishes its job. For example, in synchronous context, if Method1 calls Method2, the program control does not return to Method1 until the CLR finishes with Method2, and this makes the synchronous operation a blocking operation. In synchronous context, the program will only be allowed to do one thing at a time.

Figure 14-1 demonstrates the behavior of the synchronous operation of a program, where Method1, Method2, Method3, Method4, and Method5 are called from a program one after another. Each of the methods takes approximate 10 milliseconds to finish its operation, so the program will be blocked for about 50 milliseconds to finish execution of the five methods.

images

Figure 14-1. Synchronous method call

Listing 14-1 shows an example of the synchronous method called in a program and shows that the synchronous operation takes about 50 milliseconds to finish its execution.

Listing 14-1. Example of synchronous method call

using System;
using System.Diagnostics;
using System.Threading;

namespace Ch14
{
class Program
{
static void Main()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Method1();
Method2();
Method3();
Method4();
Method5();
stopwatch.Stop();
Console.WriteLine("Elapsed time: {0}ms",
stopwatch.ElapsedMilliseconds);
Console.ReadKey();
}
static void Method1() { Thread.Sleep(10); }
static void Method2() { Thread.Sleep(10); }
static void Method3() { Thread.Sleep(10); }
static void Method4() { Thread.Sleep(10); }
static void Method5() { Thread.Sleep(10); }
}
}

This program will produce the output (output might vary when you run it locally):

Elapsed time: 50ms

Asynchronous programming is the opposite of synchronous programming: you write long running functions asynchronously rather than synchronously. The difference with the asynchronous approach is that concurrency is initiated inside the long-running function, rather than from outside the function. Asynchronous programming is used in many programs, especially writing (typically server-side) applications that deal efficiently with a lot of concurrent input/output (I/O). The second use is to make the thread safety in rich-client applications easier.

Figure 14-2 demonstrates the behavior of the asynchronous programming, from which you can see that if Method1 starts asynchronously, then it will return the program control immediately to the place where it left to continue with other operations. When Method1 finishes its execution sometime later, it returns (or executes the callback if there is any) to the caller. While Method1 is executing, the program can deal with Method2, Method3, Method4, and Method5, as demonstrated in the Figure 14-2.

images

Figure 14-2. Asynchronous method call

As a result, while you need to wait about 50 milliseconds to finish the execution of five methods using synchronous programming, in asynchronous programming you do not need to block the program that long to continue with some other operation.

The .NET Framework allows you to execute some methods asynchronously without blocking the calling program, and these asynchronous methods might be executed on different threads, thus allowing the calling program to do its own job after the other method has been invoked.

In these kinds of asynchronous programming scenarios, in general you expect the following features to be implemented:

· Allowed to perform certain tasks (execute methods) in a different thread without blocking the calling thread

· Allowed to execute another method as a callback or continuation on completion of the asynchronous method

· Allowed to check the status of the task to determine whether the task is ready to run, it is complete, or if a fault occurred during execution

· Allowed to get information about the progress status of the task

· Allowed to cancel the task that is running

· Allowed to handle the exceptions raised during the asynchronous operation

To implement asynchronous methods using the .NET Framework, you can use the following techniques:

· Asynchronous programming model (APM)

· Event-based asynchronous pattern

· Task-based asynchronous programming

· Async-await–based asynchronous programming

This chapter will explore the asynchronous programming using async- and await-based techniques in .NET Framework 4.5. The async-based asynchronous programming works based on the Task-based asynchronous pattern using the async and await keywords. This chapter will explore Task-based programming to help you understand the beauty of the async- and await-based asynchronous programming.

Asynchronous Programming Using Task

The Task class is used to represent the asynchronous operation in .NET and was introduced in .NET Framework 4.0 as part of the parallel programming library. The Task class has a higher level of abstraction, which is used for the asynchronous operation. The tasks are compositional and use a thread pool to manage the task that is being assigned.

When you instantiate the Task class, you pass the job as an Action block and call the Start method from it to schedule the task to execute depending on the available threads. Alternatively, you can pass the instance of the Task class to some other method (which accepts Task) as a parameter to execute from there.

The Thread class can be used to perform an asynchronous operation by initiating new threads for the new operation, but it has some limitations, for example, it is hard to get data back from the thread you are waiting to Join unless you configure a shared field to pass the data around. The thread you will start cannot start a new thread by itself when it has finished its task. In addition, and most importantly, exception handling is not easy to do in concurrent programming using threading. Therefore, using the thread to write asynchronous operations becomes quite tedious and complex, and synchronization also becomes another issue. The use of the Thread also slows the performance due to the context switching from thread to thread.

The Task class eliminates most of these problems. When you instantiate the Task class, the instance of the Task will be scheduled to execute by the TaskScheduler (discussed later on this chapter), therefore, you do not need to maintain the Thread creation.

Task Class in .NET

The Task class is defined in the System.Threading.Tasks namespace of the mscorlib.dll assembly, as shown in Figure 14-3.

images

Figure 14-3. Task class in the System.Threading.Tasks namespace from the mscorlib.dll assembly

Listing 14-2 shows the methods and properties from the Task class.

Listing 14-2. Definition of the Task Class

public class Task : IThreadPoolWorkItem, IAsyncResult, IDisposable
{
/* 8 overloaded */
public Task(Action action) {}
/* 21 overloaded */
public ConfiguredTaskAwaitable ConfigureAwait(

bool continueOnCapturedContext) {}
/* 4 overloaded */
public static Task Delay(int millisecondsDelay) {}
public void Dispose() {}
public static Task<TResult> FromResult<TResult>
(TResult result) {}
public TaskAwaiter GetAwaiter() {}
/* 8 overloaded */
public static Task Run(Action action) {}
/* 2 overloaded */
public void RunSynchronously() {}
/* 2 overloaded */
public void Start() {}

/* 5 overloaded */
public void Wait() {}
/* 5 overloaded */
public static void WaitAll(params Task[] tasks) {}
/* 5 overloaded */
public static int WaitAny(params Task[] tasks) {}
/* 4 overloaded */
public static Task WhenAll(IEnumerable<Task> tasks) {}
/* 4 overloaded */
public static Task<Task> WhenAny(IEnumerable<Task> tasks) {}
public static YieldAwaitable Yield() {}
public object AsyncState {}
public TaskCreationOptions CreationOptions {}
public static int? CurrentId {}
public AggregateException Exception {}
public static TaskFactory Factory {}
public int Id {}
public bool IsCanceled {}
public bool IsCompleted {}
public bool IsFaulted {}
public TaskStatus Status {}
}

Creation/Wait of the Task

To create a task you can use the Run method from the Task class, as shown in Listing 14-3.

Listing 14-3. Task Creation Using the Run Method

using System;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
/* A task will be instantiated and scheduled to run sometime
* later on and
* return the instance of the Task to displayTask variable. */
Task displayTask = Task.Run(() =>
Console.WriteLine("async in C# 5.0"));

/* Continue until the task completes */
while (true)
{
/* To check whether the task has completed or not */
if (displayTask.IsCompleted)
{
Console.WriteLine("Task completed!");
break;
}
}
Console.ReadLine();
}
}
}

The program will produce the output:

async in C# 5.0
Task completed!

You could also use the Start method from the Task class to start a task, as demonstrated in Listing 14-4.

Listing 14-4. Task Creation Using the Task Constructor

using System;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
/* An instance of the Task will be instantiated. */
Task displayTask = new Task(
/* The Task will execute the Action block */
() => Console.WriteLine("async in C# 5.0"),
/* Task creation options */
TaskCreationOptions.None
);

displayTask.Start();
/* The Task will execute sometimes later */
displayTask.Wait();
/* Explicitly wait for the task to finish */
Console.ReadLine();
}
}
}

This will produce the output:

async in C# 5.0

Details About the Task Creation Options in Task

In Listing 14-4, the instance of the Task class displayTask was instantiated using the constructor of the Task class, which takes an Action block (the Action block contains the code of the operation the Task class will execute), and a value of the TaskCreationOptions enum, which controls the behavior for the task creation and execution. When you instantiate a task, you can use different values of the TaskCreationOptions enum, as defined in the System.Threading.Tasks namespace of the mscorlib.dll assembly. Listing 14-5 shows the different values of the TaskCreationOptions enum that can be used.

Listing 14-5. TaskStatus Enum

public enum TaskCreationOptions
{
/* It indicates that the created Task will be attached to the
* parent in the Task hierarchy */
AttachedToParent = 4,

/* It indicates to throw an InvalidOperationException when a
* child task try to attach */
DenyChildAttach = 8,

/* It prevents the immediate scheduler from being seen as the
* current scheduler in the created task.*/
HideScheduler = 0x10,

/* It indicates that the newly created Task will be long running task. */
LongRunning = 2,

/* It indicates to use the default behavior when the Task will created.*/
None = 0,

/* It indicates that the Task scheduled to run sooner will run sooner
* and the Task scheduled to run later will run later. */
PreferFairness = 1
}

Listing 14-6 demonstrates the use of the TaskCreationOptions enum.

Listing 14-6. Return Value from the Task

using System;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
Task<string> displayTask = new Task<string>(
() => "async in C# 5.0", TaskCreationOptions.None);
displayTask.Start();
Console.WriteLine("Result from the Task : {0}",
displayTask.Result);
Console.ReadLine();
}
}
}

This program will produce the output:

Result from the Task : async in C# 5.0

Details About the Status of the Task

In Listing 14-6 we instantiated the Task, which is scheduled by the CLR to execute sometime later. The Task initiator needs to know whether or not the Task has completed, is being canceled, or is being faulted while executing. To determine this, the TaskStatus enum keeps track of the status of theTask you initiated. The TaskStatus enum is defined in the System.Threading.Tasks namespace of the mscorlib.dll assembly. Listing 14-7 shows the different values of the TaskStatus enum you can use.

Listing 14-7. TaskStatus Enum

public enum TaskStatus
{
/* It indicates that the task has been canceled.*/
Canceled = 6,

/* It indicates that the CLR sets this status when the task has been
* initialized but has not been scheduled yet. */
Created = 0,

/* It indicates that the CLR sets Faulted status if there is any
* unhandled exception.*/
Faulted = 7,

/* It indicates that the task completed successfully.*/
RanToCompletion = 5,

/* It indicates that the Task is Running but has not completed yet.*/
Running = 3,

/* It indicates that the CLR scheduled the Task internally but waiting
* to be activated.*/
WaitingForActivation = 1,

/* It indicates that the CLR finished executing the Task but waiting
* for the attached child tasks to finish (if there is any).*/
WaitingForChildrenToComplete = 4,

/* It indicates that the CLR scheduled the task for execution still
* waiting to execute.*/
WaitingToRun = 2
}

The Created status is set by the CLR when you first instantiate a task, and as soon as the task starts it changes the status to WaitingToRun. The started task will be executed sometime later based on the allocated schedule for the task, and when it starts running, the CLR changes its status to Running. It will get WaitingForChildrenToComplete status if it stops running or it is waiting for any child tasks. The CLR will assign one of three statuses when it has finished the operation of the task completely:

· RanToCompletion

· Canceled

· Faulted

RanToCompletion indicates when the task has been completed successfully, Canceled indicates the task has been canceled, and Faulted indicates any exception that has occurred during the task execution. Listing 14-2 shows that the Task class has three properties:

· IsCompleted

· IsCanceled

· IsFaulted

It uses these, respectively, to determine the status of the RanToCompletion, Canceled, or Faulted properties from the Task class. Listing 14-8 shows the use of the IsCompleted, IsCanceled, and IsFaulted properties of the Task class to determine the task completion status.

Listing 14-8. Example of the IsCompleted, IsCanceled, and IsFaulted

using System;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
Task<string> displayTask = Task.Run(() =>
{
/* To simulate as doing something.*/
for (int i = 0; i <= Int16.MaxValue; ++i) ;
return "async in C# 5.0";
});
Console.WriteLine("Result from the Task : {0}",
displayTask.Result);

if (displayTask.IsCompleted)
Console.Write(TaskStatus.RanToCompletion.ToString());
if (displayTask.IsCanceled)
Console.Write(TaskStatus.Canceled.ToString());
if (displayTask.IsFaulted)
Console.Write(TaskStatus.Faulted.ToString());
Console.ReadLine();
}
}
}

This program will produce the output:

Result from the Task : async in C# 5.0
RanToCompletion

If you use the ContinueWith, ContinueWhenAll, ContinueWhenAny, or FromAsync method to instantiate a task, the CLR will use WaitingForActivation as the status of this task instance. Alternatively, if you instantiate a task using the TaskCompletionSource<TResult> object, the CLR also sets WaitingForActivation status.Listing 14-9 demonstrates the use of the different statuses of the task.

Listing 14-9. Example of the TaskStatus

using System;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
Task displayTask = Task.Run(() =>
{
Task.Delay(500000);
for (int i = 0; i < byte.MaxValue; ++i) ;
Console.WriteLine("async in C# 5.0");
});

while (true)
{
Console.WriteLine(displayTask.Status.ToString());
if (displayTask.IsCompleted)
break;
}
Console.WriteLine(displayTask.Status.ToString());
Console.ReadLine();
}
}
}

This program will produce the output:

WaitingToRun
Running
Running
Running
async in C# 5.0
Running
RanToCompletion

You have seen how to initiate an instance of the Task class and leave the instance of the task to the scheduler to execute when it has been scheduled. When the task is being scheduled, you can continue with other operations without even waiting for the task to finish. Otherwise, you can use the Wait method of the Task class to explicitly wait for the scheduled task to finish. Listing 14-10 shows the use of the Wait method from the Task class.

Listing 14-10. Wait for the Task to Finish

using System;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
Task displayTask = Task.Run(
() => Console.WriteLine("async in C# 5.0"));

Console.WriteLine(
"Task completion status : {0}, waiting to finish....",
displayTask.IsCompleted);
displayTask.Wait();
Console.ReadLine();
}
}
}

This program will produce the output:

Task completion status : False, waiting to finish....
async in C# 5.0

Continuation of the Task

In asynchronous programming, the continuation code block refers to those statements that will execute after finishing the asynchronous operation. Figure 14-4 refers to that at statement 5; the program control moves to another location to execute some operations asynchronously. It was intended that when this asynchronous operation finishes its operation, the program control will return to statement 6 to finish statements 6 to 10. This can be achieved by remembering statements 6 to 10 as a continuation code block that will be executed after the asynchronous operation has finished its operation.

images

Figure 14-4. Continuation in asynchronous method

Statements 6 through 10 will be set as a continuation code block and will execute when the program has finished the asynchronous operation defined in MethodA, as shown in Figure 14-4. You can set up the continuation of the task using the following techniques:

· Using the OnCompleted method

· Using the ContinueWith method

· Using the await statement

In the following section, we will explore these options except for the await statement, which we will explore later in this chapter.

Using the OnCompleted Method

In asynchronous programming using the Task class, you can define the continuation code block in the task to execute when the original task finishes its execution. A continuation is a callback function, which executes upon completion of the operation defined in the Task class. You can define the continuation using the OnCompleted method of the TaskAwaiter by setting the Action block of the continuation in the OnCompleted method. Listing 14-11 demonstrates the continuation of a task using the OnCompleted method.

Listing 14-11. Example of the Continuations Using Task

using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
ShowEvenNumbers();
Console.WriteLine("Waiting.....");
for (int i = 0; i <= Int16.MaxValue * 100; ++i) ;
Console.ReadLine();
}

static void ShowEvenNumbers()
{
/* A Task is instantiated*/
Task<int> evenNumbersTask = Task.Run(
() => Enumerable.Range(1,
new Random().Next(Int16.MaxValue)).Count(
item => item % 2 == 0));

/* Retrieved the awaiter from the Task instantiated earlier */
TaskAwaiter<int> awaiter = evenNumbersTask.GetAwaiter();

/* Setup the continuation block in the awaiter of the Task
* evenNumbersTask */
awaiter.OnCompleted(() =>
{
/* Continuation code block */
Console.WriteLine("Complete, Total no of even : {0}",
awaiter.GetResult());
});

/* Following line or lines is not part of the continuation
* code block in relation to the Task evenNumbersTask */
Console.WriteLine("Schedule to complete...");
}
}
}

This program will produce the output:

Schedule to complete...
Waiting.....
Complete, Total no of even : 10652

When the CLR executes the executable produced from Listing 14-11, it works as shown in Figure 14-5.

The Figure 14-5 demonstrates that:

images

Figure 14-5. Continuation and Task

1. The program control moves to the ShowEvenNumbers method.

2. It instantiates the task and gets the awaiter type TaskAwaiter<int> from the task by calling the GetAwaiter method of the task. The OnCompleted method from the TaskAwaiter<TResult> (the awaiter for the evenNumbersTask Task) is used to set the continuation code block for the task that will be executed after the job assigned to the task finishes its execution. The TaskAwaiter or TaskAwaiter<TResult> struct is defined in the System.Runtime.CompilerServices namespace of the mscorlib.dll assembly.

Listing 14-12 shows the definition of the TaskAwaiter<TResult> struct.

Listing 14-12. The TaskAwaiter Struct

public struct TaskAwaiter<TResult> :
ICriticalNotifyCompletion, INotifyCompletion
{
public bool IsCompleted {}
public void OnCompleted(Action continuation) {}
public void UnsafeOnCompleted(Action continuation) {}
public TResult GetResult() {}
}

The OnCompleted method from the TaskAwaiter<TResult> struct is used to set the continuation of the task to execute when the task should complete its execution. The OnCompleted method internally calls the OnCompletedInternal method from the TaskAwaiter<TResult> struct, as shown in Listing 14-13.

Listing 14-13. OnCompletedInternal Method

internal static void OnCompletedInternal(
Task task, Action continuation, bool continueOnCapturedContext,
bool flowExecutionContext)
{
if (continuation == null)
{
throw new ArgumentNullException("continuation");
}
StackCrawlMark lookForMyCaller = StackCrawlMark.LookForMyCaller;
if (TplEtwProvider.Log.IsEnabled(EventLevel.Verbose, ~EventKeywords.None))
{
continuation = OutputWaitEtwEvents(task, continuation);
}

/* Setup the continuation for the Task */
task.SetContinuationForAwait(
continuation, /* continuation - represents continuation code
* block which execute after finishing the Task.*/
continueOnCapturedContext,
flowExecutionContext,
ref lookForMyCaller);
}

The OnCompletedInternal method is set up in the continuation code block along with other parameters, for example, the current captured execution context (which is discussed later in this chapter), for the task. This continuation code block will execute when the original job assigned to the task finishes its execution.

3. After the CLR sets up the continuation for the task, the program control immediately returns to the initiator of the task.

We have seen how the OnCompleted method of the TaskAwaiter<TResult> struct is used to set up the continuation for the task. In addition to the OnCompleted method, you can also use the ContinueWith method from the Task class, as discussed in the next section.

Using the ContinueWith Method

To set up the continuation for the task, you can also use the ContinueWith method from the Task class. The ContinueWith method internally sets up the continuation code block for the task. Listing 14-14 shows an example of the ContinueWith method.

Listing 14-14. Example of the ContinueWith

using System;
using System.Linq;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
ShowEvenNumbers();
Console.WriteLine("Waiting.....");
for (int i = 0; i <= Int16.MaxValue * 100; ++i) ;
Console.ReadLine();
}

static void ShowEvenNumbers()
{
Task<int> evenNumbersTask = Task.Run(
() => Enumerable.Range(1,
new Random().Next(Int16.MaxValue)).
Count(item => item % 2 == 0));

evenNumbersTask.ContinueWith(task =>
{
Console.WriteLine("Complete, Total no of even : {0}",
task.Result);
});

Console.WriteLine("Schedule to complete...");
}
}
}

This program will produce the output:

Schedule to complete...
Waiting.....
Complete, Total no of even : 12896

So we have seen two different ways to set up the continuation code block for a task. We can use TaskCreationOptions to specify the behavior of the task creations; or we can specify the behavior of the task continuation using the TaskContinuationOptions enum when we set up the continuation for the task.

Details About the Task Continuation Options

The TaskContinuationOptions enum are defined in the System.Threading.Tasks namespace of the mscorlib.dll assembly, and they define the continuation behavior of the task. Listing 14-15 demonstrates the different values of the TaskContinuationOptions enum that can be used to set the continuation.

Listing 14-15. TaskContinuationOptions Enum

public enum TaskContinuationOptions
{
AttachedToParent = 4,
DenyChildAttach = 8,
ExecuteSynchronously = 0x80000,
HideScheduler = 0x10,
LazyCancellation = 0x20,
LongRunning = 2,
None = 0,
NotOnCanceled = 0x40000,
NotOnFaulted = 0x20000,
NotOnRanToCompletion = 0x10000,
OnlyOnCanceled = 0x30000,
OnlyOnFaulted = 0x50000,
OnlyOnRanToCompletion = 0x60000,
PreferFairness = 1
}

Schedule the Task Using the TaskScheduler

The CLR uses the TaskScheduler to schedule the tasks. The default task scheduler is based on the .NET Framework 4.0 ThreadPool. However, if you require a special functionality, you can create a custom scheduler and enable it for specific tasks or queries. The TaskScheduler class is defined in theSystem.Threading.Tasks namespace of the mscorlib.dll assembly. Listing 14-16 shows the definition of the TaskScheduler class.

Listing 14-16. Definition of the TaskScheduler Class

public abstract class TaskScheduler
{
protected TaskScheduler() {}
public static TaskScheduler FromCurrentSynchronizationContext() {}
protected internal virtual bool TryDequeue(Task task) {}
protected bool TryExecuteTask(Task task) {}

/* abstract methods */
protected abstract bool TryExecuteTaskInline(Task task,
bool taskWasPreviouslyQueued);
protected abstract IEnumerable<Task> GetScheduledTasks();
protected internal abstract void QueueTask(Task task);

/* Properties */
public static TaskScheduler Current {}
public static TaskScheduler Default {}
public int Id {}
public virtual int MaximumConcurrencyLevel {}

/*Event in TaskScheduler class */
public static event EventHandler<UnobservedTaskExceptionEventArgs>
UnobservedTaskException {}
}

In .NET, there are two kinds of task schedulers:

· The thread pool task scheduler

· A synchronization context task scheduler

In .NET, all the applications by default use the ThreadPool task scheduler, which maintains a global FIFO (first-in-first-out) work queue. The FIFO is maintained for threads in each of the application domains. On call of the QueueUserWorkItem (or UnsafeQueueUserWorkItem) method, the CLR keeps the task on this shared queue, which will be dequeued to process later.

When the CLR queues the task into the queue, it follows two threads:

· Top-level tasks, which are tasks that are not created in the context of another task, are put on the global queue.

· Child or nested tasks, which are created in the context of another task, are put on a local queue that is specific to the thread on which the parent task is executing. The local queue works as LIFO (last-in-first-out).

When the thread is ready to work, the CLR looks in the local queue first, and if there are any work items waiting to be executed, the CLR will accessed those. Figure 14-6 shows the TaskScheduler queue strategy for the top level tasks and child task that has been instantiated in context of another task.

The use of local queues not only reduces pressure on the global queue, but it also takes advantage of data locality.

images

Figure 14-6. TaskScheduler

ExecutionContext to Capture the Context

Every thread has an execution context data structure associated with it, which is the ExecutionContext class that defines in the System.Threading namespace of the mscorlib.dll assembly. The ExecutionContext class allows the user code to capture and transfer the context of the execution across a user-defined asynchronous point. This captured context can be accesses from somewhere else and used later to execute the operation. Listing 14-17 shows the definition of the ExecutionContext class.

Listing 14-17. Definition of the ExecutionContext Class

public sealed class ExecutionContext : IDisposable, ISerializable
{
public static ExecutionContext Capture() {}
public ExecutionContext CreateCopy() {}
public void Dispose() {}
public void GetObjectData(SerializationInfo info,
StreamingContext context) {}
public static bool IsFlowSuppressed() {}
public static void RestoreFlow() {}
public static void Run(ExecutionContext executionContext,
ContextCallback callback, object state) {}
public static AsyncFlowControl SuppressFlow() {}
}

The execution context includes all the information relevant to a logical thread execution, for example, security settings (compressed stack, thread’s principal property, and Windows identity), host settings, and logical call context data. The ExecutionContext is used to capture the current execution context and pass it to relevant places to execute from there. For example, you can pass captured ExecutionContext to the asynchronous point to execute some action block on the captured execution context. Transferring the execution context by setting it from one thread to another causes exceptions unless you copy across the execution context. Listing 14-18 demonstrates where a state machine is running in the captured execution context.

Listing 14-18. Example of the State Machine Run on the Captured Execution Context

using System;
using System.Threading;

namespace Ch14
{
class Program
{
static void Main()
{
/*Instantiates an instance of the State machine */
StateMachine sm = new StateMachine();

/*Initialize the State machine with it's default value*/
sm.State = 1;

/* Start the State machine */
sm.MoveNext();

/* Queue the captured execution context with the workitem in the
* ThreadPool to process later. */
ThreadPool.QueueUserWorkItem(state =>
RunLaterOn<object>(
/* Get the current execution context */
CaptureExecutionContext(),
/* pass the callback code block */
new Action<object>(Callback),
/* The State machine which maintains the state */
sm));

/* Do something else. */
for (int i = 0; i <= Int16.MaxValue; ++i)
if (i % byte.MaxValue == 0)
Console.Write(".");
Console.ReadLine();
}

/* This code block capture the current execution context */
static ExecutionContext CaptureExecutionContext()
{
return ExecutionContext.Capture();
}

/* This code block will run the callback code block on the
* captured execution context*/
static void RunLaterOn<T>(
ExecutionContext context, Action<T> callback, object state)
{
ExecutionContext.Run(context,
new ContextCallback(Callback), state);
}

/* This code block used as the callback */
static void Callback(object state)
{
((StateMachine)state).MoveNext();
}
}

/* The State machine used to maintain the state of the operation */
public class StateMachine
{
public int State { get; set; }

public void MoveNext()
{
switch (State)
{
case 0:
Console.Write("State 0");
State = 1;
break;
case 1:
Console.Write("State 1");
for (int i = 0; i <= byte.MaxValue; ++i) ;
State = 0;
break;
default:
Console.Write("State -1");
State = 10;
break;
}
}
}
}

This program will produce the output:

State 1.................................................................................State 0.
...............................................

In Listing 14-18, a state machine has been instantiated with its initial state 1 in the Main method of the Program class. The CLR then calls the MoveNext method from the instance of the State Machine to change the state of the state machine. The current execution context has been captured using theCaptureExecutionContext method. This captured execution context and a callback method have been passed to the ThreadPool to queue a new work item to schedule to run later. As soon as it is queued in the ThreadPool to run later, the program control will return to the caller (i.e., Main method) to finish the rest of the code, which is the for loop block.

When the item queued in the ThreadPool executes, it will run the callback method and change the state of the state machine based on the state it was in before using the captured execution context.

Exceptions of the Task

One of the main reasons to use the Task class is to make exception handling in the concurrent programming easy. When you use the Task class, the CLR propagates the unhandled exception thrown from the code block that is running inside a task back to the thread from which the task was instantiated. If you use the Wait method from the task or the Task<TResult> class in the program, the CLR will propagate the exceptions, which you can handle using the try-catch block. The task calling code block will be inside the try block and exception handling will be inside the catch block.

The CLR aggregates all the exceptions in an AggregateException class, which is defined in the System namespace of the mscorlib.dll assembly. Listing 14-19 shows the definition of the AggregateException class.

Listing 14-19. Definition of the AggregateException Class

public class AggregateException : Exception
{
/* 7 overloaded constructors*/
public AggregateException() {}
public AggregateException Flatten() {}
public override Exception GetBaseException() {}
public override void GetObjectData(
SerializationInfo info, StreamingContext context) {}
public void Handle(Func<Exception, bool> predicate) {}
public override string ToString() {}
public ReadOnlyCollection<Exception> InnerExceptions {}
}

The AggregateException class contains the InnerExceptions property where the CLR puts all the aggregated exceptions. You can get all the original exceptions back after enumerating in the InnerExceptions from the AggregateException class. Listing 14-20 shows the use of the AggregateException class.

Listing 14-20. Exception Handling in the Task

using System;
using System.Linq;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
int limit = new Random().Next(Int32.MaxValue);

Task<int> evenNumbersTask = Task.Run(
() => Enumerable.Range(1, limit).Count(item =>
{
if (item == Int16.MaxValue)
/* Out of Range will be concat with the original
* Framework given exception message */
throw new ArgumentOutOfRangeException(
"Out of Range....");
return item % 2 == 0;
}));
try
{
evenNumbersTask.Wait(); /* Wait for the Exception to occur. */
}
catch (AggregateException aggregateException)
{
aggregateException
.InnerExceptions
.Where(item => item is ArgumentOutOfRangeException)
.ToList() /* Contains ArgumentOutOfRangeException */
.ForEach(age => Console.WriteLine(age.Message));
}
}
}
}

This program will produce the output:

Specified argument was out of the range of valid values.
Parameter name: Out of Range....

UnobservedTaskException of the Task

You use the UnobservedTaskException event from the TaskScheduler class at a global level to subscribe to unobserved exceptions. The handler of this event can be used to log the error.

Exception of the Task that Attached with Child

Listing 14-21 shows an example of the exception handling for the task that attached with child.

Listing 14-21. Example of the Exception for the Task that Attached with Child

using System;
using System.Linq;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
int limit = new Random().Next(Int32.MaxValue);

var evenNumbersTask = Task.Factory.StartNew(() =>
{
var r = Task.Factory.StartNew(() =>
{
Enumerable.Range(1, limit).Count(item =>
{
if (item == Int16.MaxValue)
/* Out of Range will be concat with the original
* Framework given exception message */
throw new ArgumentOutOfRangeException(
"Out of Range....");
return item % 2 == 0;
});
}, TaskCreationOptions.AttachedToParent);
}, TaskCreationOptions.AttachedToParent);

try
{
evenNumbersTask.Wait(); /* Wait for the Exception to occur. */
}
catch (AggregateException aggregateException)
{
aggregateException
.Flatten()
.InnerExceptions
.Where(item => item is ArgumentOutOfRangeException)
.ToList() /* Contains ArgumentOutOfRangeException */
.ForEach(age => Console.WriteLine(age.Message));
}
}
}
}

This program will produce the output:

Specified argument was out of the range of valid values.
Parameter name: Out of Range....

Exception of the Task that Detached with Child

Listing 14-22 shows an example of the exception handling for the task that detached with child.

Listing 14-22. Example of the Exception of the Task that Detached with Child

using System;
using System.Linq;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
int limit = new Random().Next(Int32.MaxValue);

var evenNumbersTask = Task.Factory.StartNew(() =>
{
var r = Task.Factory.StartNew(() =>
{
Enumerable.Range(1, limit).Count(item =>
{
if (item == Int16.MaxValue)
/* Out of Range will be concat with the original
* Framework given exception message */
throw new ArgumentOutOfRangeException(
"Out of Range....");
return item % 2 == 0;
});
});
r.Wait();
});

try
{
evenNumbersTask.Wait(); /* Wait for the Exception to occur. */
}
catch (AggregateException aggregateException)
{
aggregateException
.Flatten()
.InnerExceptions
.Where(item => item is ArgumentOutOfRangeException)
.ToList() /* Contains ArgumentOutOfRangeException */
.ForEach(age => Console.WriteLine(age.Message));
}
}
}
}

This program will produce the output:

Specified argument was out of the range of valid values.
Parameter name: Out of Range....

Asynchronous Programming Using Async and Await

Earlier in this chapter you learned how you can write asynchronous method using the Task class by setting the continuation code block manually. In this section, you will explore how to use the new features of C# 5.0—async and await keywords—to write the asynchronous method without having to manually set up the continuation code block. Using these keywords, you will be able to write asynchronous code that has the same structure and simplicity as the synchronous code you would write.

Asynchronous functions in C# 5.0 are easy to write and handle in application, for example, I/O-bound and CPU-bound applications. As discussed earlier, when you use the Task class for asynchronous operations, you can assign continuation of the asynchronous operation, but you need to set that up manually. When using the await expression in the asynchronous function, you can leave the continuation set up to the C# compiler who will set up the continuation code block for you. The C# compiler implements the continuation code block using the rest of the code after the firstawait statement it finds. Therefore, the responsibility goes to the C# compiler, not you, to set up the continuation block for the asynchronous functions.

Because you are able to leave this task to the C# compiler, you can write code that maintains its logical structure and eliminate many flow issues you would have in asynchronous operations. Asynchronous function uses async and await keywords, which need to have a few elements in them:

· An asynchronous function defined by async and await in C# must have possible return types of either void or one of the types of Task or Task <T>.

· If you want to create a function, an asynchronous function needs to be marked with the async modifier; without the async modifier, a function would be called synchronous.

· The execution of the asynchronous methods is different from synchronous methods because their execution is discontinuous. Using the await statement, the execution of the asynchronous method can be suspended and can resume the execution using the continuation.

The following sections will explore more in depth about the async and await keywords and their usage, and later we will explore the behind-the-scenes operation of the asynchronous implementation mechanism in C# 5.0, for example, how the asynchronous behavior is implemented using the state machine and how the asynchronous task gets scheduled to the ThreadPool.

The Async Modifier

The async is the modifier used to mark a method as an asynchronous function, so a function without the async modifier is called a synchronous function. The grammar for the async modifier is:

· When the async modifier is used in a method: async(optional) with method signature

· When the async modifier is used in a lambda expression: async (optional) with anonymous-function-signature => anonymous-function-body

· When the async modifier is used in anonymous method expression: async(optional) with delegate explicit-anonymous-function-signature (optional) block

Listing 14-23 shows an example of asynchronous method defined using the async modifier.

Listing 14-23. Example of the Async

using System;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
Task showNumber = ShowEvenNumbers();
Console.ReadLine();
}

/* The async modifier used to define the asynchronous method */
public static async Task ShowEvenNumbers()
{
await Task.Run(() => Console.WriteLine("Async Function"));
}
}
}

This program will produce the output:

Async Function

Listing 14-24 shows another example of the asynchronous method that used the async modifier to define it and it returns Task.

Listing 14-24. Example of the Asynchronous Method with Different Return Statement

using System;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
Task showNumber = ShowEvenNumbers();
Console.ReadLine();
}

/* The async modifier used to define the asynchronous method */
public static async Task<int> ShowEvenNumbers()
{
return await Task.Run(() =>
{
Console.WriteLine("Async Function");
});
}
}
}

Listing 14-24 produced the following compile time error, as the return statement does not return any integer value from the ShowEvenNumbers method.

Error 150 Cannot implicitly convert type 'void' to 'int'

If you use the Task<T> as the return type from the asynchronous method, the return expression must be implicitly convertible to type of T, for example, if the return is Task<string>, then the return expression of the asynchronous method must be string.

Invocation of the task returning asynchronous method initially is the same as the synchronous function until the CLR finds the first await expression.

The Await

The await expression is used to suspend the execution of an asynchronous function until the awaited task completes its operation. In Listings 14-23 and 14-24, the await keyword is used in the asynchronous method. The grammar of the await expression is:

unary-expression:


await-expression
await-expression:
await unary-expression
statement-expression:

await-expression

The await expression is allowed when the await is used in a method marked with the async modifier, otherwise the compiler generates a compile time error:

The 'await' operator can only be used within an async method

The await keyword is used to await a task if the Task (T) is used in an await expression (AE), for example:

AE = await T

The T then has to have a GetAwaiter method with no parameter but it returns a type of R, which has the following accessible members:

bool IsCompleted {get;}
void OnCompleted (Action);
void GetResult(); or
o GetResult(); where o refers to a type

When the C# compiler finds an await keyword in source code, it compiles the await statement, for example:

Get a awaiter (A) from the Task (T)
If A exists in T then
Set the continuation using the OnCompleted method from the A.

The C# compiler then evaluates the await expression:

1. An awaiter A of type TaskAwaiter is obtained from the expression followed by the await Task T. The TaskAwaiter is used to set up the continuation for the asynchronous function and gets the result from the asynchronous function. The TaskAwaiter is defined as a struct in the mscorlib.dllassembly, with the definition shown in Listing 14-25.

Listing 14-25. Definition of the TaskAwaiter

public struct TaskAwaiter : ICriticalNotifyCompletion, INotifyCompletion
{
public bool IsCompleted {}
public void OnCompleted(Action continuation) {}
public void UnsafeOnCompleted(Action continuation) {}
public void GetResult() {}
}

2. The CLR then checks the status of the task by checking the IsCompleted property from the awaiter A to see whether or not the task has completed.

3. On return of true from the IsCompleted property, the associated continuation will execute, otherwise on false, the execution will suspended and the program control will be returned to the method that invoked this asynchronous method.

4. When the awaited task completes, the CLR executes the GetResult method from the TaskAwaiter struct and returns the result to the caller of the asynchronous function, otherwise the result is nothing.

Analysis of the Async- and Await-Based Asynchronous Program

We have explored two of the key feature in C#, async and await, in the previous section. This section will explore this in more detail how the C # compiler compiles async- and await-based asynchronous methods.

Listing 14-26 demonstrates the async- and await-based asynchronous methods. This program determines the number of even numbers from a specified range of numbers. While the program executes the asynchronous operation in the ShowEvenNumbers method, the for loop block of the Main method will act as if it were doing some other long running operation to let you get the output from the asynchronous operation.

Listing 14-26. Example of the Asynchronous Program

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
Console.WriteLine("{0,15}{1,46}{2,15}",
"Method", "Description", "Thread Id");
Console.WriteLine("{0,15}{1,46}{2,15}",
"Main", "Start Processing.....",
Thread.CurrentThread.ManagedThreadId);

/* Call an async method */
Task showNumber = ShowEvenNumbers();

/* The for loop used to simulate as something else is executing
* while the asynchronous operation is executing it's task.*/
for (int i = 0; i < Int64.MaxValue; ++i)
if (i % Int32.MaxValue == 0)
Console.WriteLine("{0,15}{1,46}{2,15}",
"Main", "something else is doing......",
Thread.CurrentThread.ManagedThreadId);
/* Checking the Task whether it's completed or not */
else if (showNumber.IsCompleted)
break;

Console.WriteLine("{0,15}{1,46}{2,15}",
"Main", "Finished execution",
Thread.CurrentThread.ManagedThreadId);
Console.ReadLine();
}

public static async Task ShowEvenNumbers()
{
Console.WriteLine("{0,15}{1,46}{2,15}", "ShowEvenNumbers",
"Processing is continuing.....",
Thread.CurrentThread.ManagedThreadId);
int limit = new Random().Next(Int32.MaxValue);
string range = string.Format("({0},{1})", 1, limit);

/* Initialize and schedule a Task to run sometime later on.*/
Task<int> evenNumbersTask = Task.Run(
() => Enumerable.Range(1, limit).Count(item => item % 2 == 0));

/* The await statement await the Task to complete later by
* set up the continuation code block to execute after the
* Task finishes it's job.*/
int count = await evenNumbersTask;

/* Following code block will be used as the continuation code
* block for the evenNumbersTask and it will be setup by
* the C# compiler. */
Console.WriteLine("{0,15}{1,46}{2,15}", "ShowEvenNumbers",
string.Format("In {0} Total: {1} On Thread", range, count),
Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("{0,15}{1,46}{2,15}", "ShowEvenNumbers",
"Processing is finished.....",
Thread.CurrentThread.ManagedThreadId);
}
}
}

This program will produce the output.

Method Description Thread Id
Main Start Processing..... 1
ShowEvenNumbers Processing is continuing..... 1
Main something else is doing...... 1
Main something else is doing...... 1
Main something else is doing...... 1
ShowEvenNumbers In (1,1507146102) Total: 753573051 On Thread 3
ShowEvenNumbers Processing is finished..... 3
Main Finished execution 1

From the output of Listing 14-26, you can see that the Main method starts the processing and calls the ShowEvenNumbers method, which begins the process by assigning the Task to the scheduler to execute when it is able to and then returning to the Main method to do something else. All these operations are executing in the same thread, as shown in the output of Listing 14-26. You can see that sometime later the ShowEvenNumbers method shows it has processed the result and continues executing its remaining operation, for example, to display the "Processing is finished" on the console from the continuation of the ShowEvenNumbers method. All the processing for the ShowEvenNumbers was executing in thread 3, which is different from others, but the scheduler control continues to allocate the scheduled task on the available thread. Figure 14-7 demonstrates the basic control flow of theasync method in C#.

images

Figure 14-7. Basic program flowing an asynchronous program

When Listing 14-26 starts execution, it starts from the Main method and continues the execution until it comes to step 1. From there the CLR executes the program as described in these steps, as shown with the numbers in Figure 14-7:

1. The program control moves to the ShowEvenNumbers method.

2. The CLR continues executing the statement from the ShowEvenNumbers method, for example, displaying the initial "Processing is continuing" message on the console, generating the Random number, and instantiating the task.

3. As soon as the CLR comes to the await expression, to execute the task, it assigns the continuation to the awaiter of the task and suspends the execution.

4. The program control immediately returns to the Main method, to the point from which it left, to the ShowEvenNumbers method in step 1.

5. The CLR will continue with the rest of the statements from the Main method.

6. Sometimes later, when the awaited task (in step 3) has been completed, it will come to the point marked as step 6 in Figure 14-7 to finish the execution of the rest of its operation, which includes all the statements after the await expression in the ShowEvenNumbers method set up, as the continuation in step 3.

7. The CLR finishes the ShowEvenNumbers method.

This explains the very basic high-level workflow used in the async- and await-based asynchronous programming in C#. The following sections will explore more about the operations of these asynchronous methods in C#.

Async Behind the Scenes

In the .NET Framework 4.5, any method marked with the async keyword is considered an asynchronous method in C#. The C# compiler will perform the necessary transformations to implement the method asynchronously using the task asynchronous pattern (TAP).

The C# compiler compiles the asynchronous method into two important code blocks:

· Stub method contains the initial set up information for the asynchronous method, including generating the code related to the AsyncTaskMethodBuilder class, and starts the state machine generated by the C# compiler for the async-based method.

· State machine maintains the state of the asynchronous method, for example, as discussed earlier, the async-based method resumes its execution by assigning the continuation to process it later. This state machine maintains the state in which the method was before the resume was initiated, where this will be after the resume is initiated, or what it will do when it finishes its execution.

This section will explore the details how the C# compiler generates the necessary code for the async-based method and how this generated code works in runtime.

Async in Compile Time

Figure 14-8 demonstrates how the async-based method is recompiled by the C# compiler. The .NET Reflector tool is used to decompile the executable produced from Listing 14-26 to explore the stub method and state machine generated for the ShowEvenNumbers method, as demonstrated in Figure 14-8.

images

Figure 14-8. The C# compiler generated code for the async method

Figure 14-8 demonstrates that the C# compiler compiles the ShowEvenNumbers method into two code blocks:

· Stub method is used to initiate the state machine that the C# compiler generated for the ShowEvenNumbers method. It will initiate the state machine with the default value –1 and start the state machine.

· State machine, which is the heart of the ShowEvenNumbers asynchronous method, maintains the state of the operation to use later when it resumes the operation.

The following section explores the C# compiler–generated stub method and the state machine for the ShowEvenNumbers method.

Stub Method of the Async Method

The C# compiler generates the stub method, as shown in Listing 14-27, for the ShowEvenNumbers asynchronous method presented in Listing 14-26.

Listing 14-27. IL Version of the Stub Method

.method public hidebysig static class
[mscorlib]System.Threading.Tasks.Task
ShowEvenNumbers() cil managed
{
/* Code removed */
.maxstack 2
.locals init (
[0] valuetype Ch14.Program/<ShowEvenNumbers>d__5 d__,
[1] class [mscorlib]System.Threading.Tasks.Task task,
[2] valuetype
[mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
builder)

L_0000: ldloca.s d__

/* It creates AsyncTaskMethodBuilder and store into
* the <>t__builder field */
L_0002: call valuetype
[mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
[mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::
Create()

L_0007: stfld valuetype
[mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
Ch14.Program/<ShowEvenNumbers>d__5::<>t__builder

L_000c: ldloca.s d__

/* Initiates the state machine with it's default value -1 */
L_000e: ldc.i4.m1
L_000f: stfld int32 Ch14.Program/<ShowEvenNumbers>d__5::<>1__state

L_0014: ldloca.s d__
L_0016: ldfld valuetype
[mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
Ch14.Program/<ShowEvenNumbers>d__5::<>t__builder
L_001b: stloc.2
L_001c: ldloca.s builder
L_001e: ldloca.s d__

/* Start the state machine by executing the Start method from
* the AsyncTaskMethodBuilder*/
L_0020: call instance
void
[mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::
Start<valuetype Ch14.Program/<ShowEvenNumbers>d__5>(!!0&)

L_0025: ldloca.s d__
L_0027: ldflda valuetype
[mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
Ch14.Program/<ShowEvenNumbers>d__5::<>t__builder

/* Get the Task which has been initialized and scheduled to execute
* later on */
L_002c: call instance class
[mscorlib]System.Threading.Tasks.Task
[mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::
get_Task()

L_0031: stloc.1
L_0032: br.s L_0034
L_0034: ldloc.1
L_0035: ret
}

This stub method is an important bit of code that the C# compiler generates for the ShowEvenNumbers async-based method, which is responsible for initiating the generated state machine <ShowEvenNumbers>d__5 with its initial value –1 set in L_000f by setting it to the <>1__state field of the state machine. InL_0020, it will call the Start method from the AsyncTaskMethodBuilder class to start the state machine <ShowEvenNumbers>d__5. Later it calls the get_Task method from the AsyncTaskMethodBuilder class to get the task (whether or not it is completed), which will be used to access the result if there is any from the Taskclass or to get the status of the task.

State Machine of the Async Method

Listing 14-28 shows the C# compiler–generated state machine <ShowEvenNumbers>d__5 for Listing 14-26.

Listing 14-28. The Compiler-Generated State Machine

private struct <ShowEvenNumbers>d__5 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
private object <>t__stack;
private TaskAwaiter<int> <>u__$awaiter9;
public int <count>5__8;
public Task<int> <evenNumbersTask>5__7;
public string <range>5__6;
public Program.<>c__DisplayClass2 CS$<>8__locals3;

private void MoveNext()
{
try
{
TaskAwaiter<int> CS$0$0001;
bool <>t__doFinallyBodies = true;

switch (this.<>1__state)
{
case -3:
goto Label_0196;

case 0:
break;

default:
this.CS$<>8__locals3 = new Program.<>c__DisplayClass2();

Console.WriteLine("{0,15}{1,46}{2,15}", "ShowEvenNumbers",
"Processing is continuing.....",
Thread.CurrentThread.ManagedThreadId);

this.CS$<>8__locals3.limit = new Random().Next(0x7fffffff);
this.<range>5__6 = string.Format("({0},{1})",
1, this.CS$<>8__locals3.limit);

this.<evenNumbersTask>5__7 = Task.Run<int>(
new Func<int>(
this.CS$<>8__locals3.<ShowEvenNumbers>b__0));

CS$0$0001 = this.<evenNumbersTask>5__7.GetAwaiter();

if (CS$0$0001.IsCompleted)
{
goto Label_0104;
}
this.<>1__state = 0;
this.<>u__$awaiter9 = CS$0$0001;

this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>,
Program.<ShowEvenNumbers>d__5>(
ref CS$0$0001, ref this);
<>t__doFinallyBodies = false;

return;
}

CS$0$0001 = this.<>u__$awaiter9;
this.<>u__$awaiter9 = new TaskAwaiter<int>();
this.<>1__state = -1;

Label_0104:
int introduced6 = CS$0$0001.GetResult();

CS$0$0001 = new TaskAwaiter<int>();

int CS$0$0003 = introduced6;
this.<count>5__8 = CS$0$0003;
Console.WriteLine("{0,15}{1,46}{2,15}", "ShowEvenNumbers",
string.Format("In {0} Total: {1} On Thread",
this.<range>5__6, this.<count>5__8),
Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("{0,15}{1,46}{2,15}", "ShowEvenNumbers",
"Processing is finished.....",
Thread.CurrentThread.ManagedThreadId);
}
catch (Exception <>t__ex)
{
this.<>1__state = -2;
this.<>t__builder.SetException(<>t__ex);
return;
}
Label_0196:
this.<>1__state = -2;
this.<>t__builder.SetResult();
}

/* Code removed */
}

Fields in the State Machine

The C# compiler will generate fields for the state machine for each of the fields it owns, as demonstrated in Table 14.1.

images

images

Depending on the circumstances and number of local variable used in the async method, the fields may be different.

Methods in the State Machine

The C# compiler generates two methods for the state machine:

· MoveNext: This method contains the switch block to control the operation based on the state of the state machine.

· SetStateMachine: This method is used to set the state machine.

Async in Runtime

When the CLR starts executing the async-based asynchronous method, it starts execution from the stub method to initiate and start the state machine, schedule the task, and set up the continuation for the task if there is any and leaves the asynchronous task to the scheduler to execute later when it is scheduled. The CLR:

· Starts the state machine

· Schedules the task

· Sets up the Continuation

· Executes the task

· Executes the continuation

This section will explore the details involved in these steps to show how the CLR executes the async-based asynchronous method in runtime. The CLR instantiates an instance of the AsyncTaskMethodBuilder class in L_0002, shown in Listing 14-27, by calling the Create method of theAsyncTaskMethodBuilder class. The Create method then instantiates an instance of the AsyncTaskMethodBuilder class:

public static AsyncTaskMethodBuilder Create()
{
return new AsyncTaskMethodBuilder();
}

After instantiating the AsyncTaskMethodBuilder class, the CLR executes the rest of the asynchronous code as described in the sections that follow.

Start the State Machine

The CLR uses the AsyncTaskMethodBuilder created earlier to start the state machine <ShowEvenNumbers>d__5 by calling the Start method from it (step 1 in Figure 14-9). The CLR passes the initialized state machine <ShowEvenNumbers>d__5 from the stub method as input to the Start method of theAsyncTaskMethodBuilder class. Listing 14-29 shows the Start method from the AsyncTaskMethodBuilder class.

Listing 14-29. Start Method from the AsyncTaskMethodBuilder Class

.method public hidebysig instance void Start<(System.Runtime.CompilerServices.
IAsyncStateMachine) TStateMachine>(!!TStateMachine& stateMachine) cil managed
{
.custom instance void
System.Diagnostics.DebuggerStepThroughAttribute::.ctor()
.custom instance void __DynamicallyInvokableAttribute::.ctor()
.maxstack 8
L_0000: ldarg.0
L_0001: ldflda valuetype
System.Runtime.CompilerServices.AsyncTaskMethodBuilder'1
<valuetype System.Threading.Tasks.VoidTaskResult>
System.Runtime.CompilerServices.AsyncTaskMethodBuilder::m_builder
L_0006: ldarg.1
L_0007: call instance void
System.Runtime.CompilerServices.AsyncTaskMethodBuilder'1
<valuetype System.Threading.Tasks.VoidTaskResult>::
Start<!!TStateMachine>(!!0&)
L_000c: ret
}

The Start method internally calls the Start method from AsyncTaskMethodBuilder<VoidTaskResult> class in L_0007 from Listing 14-29. The Start method of AsyncTaskMethodBuilder<VoidTaskResult> class is demonstrated in Listing 14-30.

Listing 14-30. Start Method from the AsyncTaskMethodBuilder<TResult> Class

.method public hidebysig instance void Start
<(System.Runtime.CompilerServices.IAsyncStateMachine) TStateMachine>
(!!TStateMachine& stateMachine) cil managed
{
.custom instance void System.Diagnostics.DebuggerStepThroughAttribute::.ctor()
.custom instance void __DynamicallyInvokableAttribute::.ctor()
.maxstack 8
L_0000: ldarg.0

L_0001: ldflda valuetype
System.Runtime.CompilerServices.AsyncMethodBuilderCore
System.Runtime.CompilerServices
.AsyncTaskMethodBuilder'1<!TResult>::m_coreState
L_0006: ldarg.1

/* Call the Start method of the AsyncMethodBuilderCore which will
* start the given state machine.*/
L_0007: call instance
void System.Runtime.CompilerServices.AsyncMethodBuilderCore::
Start<!!TStateMachine>(!!0&)
L_000c: ret
}

From Listing 14-30, the CLR calls the Start method of the AsyncMethodBuilderCore class in L_0007 by passing the state machine it received as input. The Start method internally works as demonstrated in the decompiled Start method code, as shown in Listing 14-31.

Listing 14-31. Start Method from the AsyncMethodBuilderCore Class

.method assembly hidebysig instance void Start
<(System.Runtime.CompilerServices.IAsyncStateMachine) TStateMachine>
(!!TStateMachine& stateMachine) cil managed
{
.custom instance void
System.Security.SecuritySafeCriticalAttribute::.ctor()
.custom instance void
System.Diagnostics.DebuggerStepThroughAttribute::.ctor()
.maxstack 3
.locals init (
[0] class System.Threading.Thread thread,
[1] valuetype System.Threading.ExecutionContextSwitcher switcher)
/* Code removed */

/* The CLR calls the MoveNext method from the state machine to do a
* transition in the state machine */
L_003b: callvirt instance void
System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext()

/* Code removed */
.try L_002b to L_0042 finally handler L_0042 to L_004b
}

The Start method of the AsyncMethodBuilderCore class calls the MoveNext method from the state machine passed to it. Figure 14-9 demonstrates the execution of the stub method and state machine <ShowEvenNumbers>d__5 in runtime.

images

Figure 14-9. Stubs (Listing 14-27) and state machine (Listing 14-28) of the asynchronous method in runtime

In step 2 of Figure 14-9, the CLR comes to the state machine with its default value –1 (set from the stub method) and executes the default section of the switch block from the state machine <ShowEvenNumbers>d__5. The CLR instantiated an instance of the Task class with the provided operation it has to complete via the Run method of the Task class, which internally calls the StartNew method from the Task class:

public static Task<TResult> Run<TResult>(Func<TResult> function)
{
StackCrawlMark lookForMyCaller = StackCrawlMark.LookForMyCaller;
return Task<TResult>.StartNew(
null,
function,
new CancellationToken(),
TaskCreationOptions.DenyChildAttach,
InternalTaskOptions.None,
TaskScheduler.Default,
ref lookForMyCaller);
}

The StartNew method initiates a task and schedules the newly created task by calling the ScheduleAndStart method from the Task class:

internal static Task<TResult> StartNew(
Task parent,
Func<TResult> function,
CancellationToken cancellationToken,
TaskCreationOptions creationOptions,
InternalTaskOptions internalOptions,
TaskScheduler scheduler,
ref StackCrawlMark stackMark)
{
/*Code removed*/
Task<TResult> task = new Task<TResult>(
function,
parent,
cancellationToken,
creationOptions,
internalOptions | InternalTaskOptions.QueuedByRuntime,
scheduler,
ref stackMark);

/* Schedules the Task to execute when it is scheduled or sometime
* later on */
task.ScheduleAndStart(false);
return task;
}

Schedule the Task

In step 3 in Figure 14-9, the ScheduleAndStart method from the Task class queues the task using the QueueTask method of the ThreadPoolTaskScheduler class, which it inherited from the TaskScheduler class. The m_taskScheduler field from the Task class holds the TaskScheduler class. The QueueTask is an abstract method defined in the TaskScheduler class:

protected internal abstract void QueueTask(Task task);

Let’s look at the implementation of the QueueTask method from the ThreadPoolTaskScheduler class:

protected internal override void QueueTask(Task task)
{
TplEtwProvider log = TplEtwProvider.Log;
if (log.IsEnabled(EventLevel.Verbose, ~EventKeywords.None))
{
Task internalCurrent = Task.InternalCurrent;
Task parent = task.m_parent;
log.TaskScheduled(base.Id,
(internalCurrent == null) ? 0 : internalCurrent.Id,
task.Id,
(parent == null) ? 0 : parent.Id,
(int) task.Options);
}
if ((task.Options & TaskCreationOptions.LongRunning) !=
TaskCreationOptions.None)
{
new Thread(s_longRunningThreadWork)
{ IsBackground = true }.Start(task);
}
else
{
bool forceGlobal = (task.Options & TaskCreationOptions.PreferFairness)
!= TaskCreationOptions.None;
ThreadPool.UnsafeQueueCustomWorkItem(task, forceGlobal);
}
}

The implementation of the UnsafeQueueCustomWorkItem method of the ThreadPool class is:

internal static void UnsafeQueueCustomWorkItem
(IThreadPoolWorkItem workItem, bool forceGlobal)
{
EnsureVMInitialized();
try
{
}
finally
{
ThreadPoolGlobals.workQueue.Enqueue(workItem, forceGlobal);
}
}

The field workQueue is from the ThreadPoolWorkQueue class, which calls the Enqueue method and eventually queues the task into the ThreadPool:

public void Enqueue(IThreadPoolWorkItem callback, bool forceGlobal)
{
/* Code removed */
else
{
QueueSegment queueHead = this.queueHead;
while (!queueHead.TryEnqueue(callback))
{
Interlocked.CompareExchange<QueueSegment>(
ref queueHead.Next, new QueueSegment(), null);
while (queueHead.Next != null)
{
Interlocked.CompareExchange<QueueSegment>(
ref this.queueHead, queueHead.Next, queueHead);
queueHead = this.queueHead;
}
}
}
this.EnsureThreadRequested();
}

Setting Up the Continuation

In step 4 of Figure 14-9, the CLR returns the initialized task. In step 5, after finishing the task initialization and scheduling, the CLR checks whether or not the task has been completed by checking the awaiter return from the task in step 4. If the task has not completed, then it sets up the continuation or resumption code, or callback code block in the task.

In step 6, the program control immediately transfers back to the async stub method at the point from which the CLR called the Start method (in L_0020 from the stub method code in Listing 14-27).

In step 7, the CLR gets the task from the AsyncTaskMethodBuilder by calling the get_Task method in L_002c and returns it to the caller of the async method ShowEvenNumbers. The CLR then continues with the operation (if there is any) from the caller method, which is the Main method.

In step 8, sometime later, the task scheduled in the ThreadPool will be triggered by the operating system (OS) scheduler to start execution, as demonstrated in the code:

internal static class _ThreadPoolWaitCallback
{
[SecurityCritical]
internal static bool PerformWaitCallback()
{
return ThreadPoolWorkQueue.Dispatch();
}
}

Executing the Scheduled Task

In step 9, the CLR dequeues the task from the ThreadPool and executes the operation assigned to the task, for example, the b__1 method, as shows in the code:

private static bool <ShowEvenNumbers>b__1(int item)
{
return ((item % 2) == 0);
}

In step 10, the CLR executes the b__1 method and sets the new state into the state machine. It calls the MoveNext method of the state machine to move into the next state and executes the relevant code with the associate state.

In step 11, the continuation or resumption code block will be executed to finalize the async method processing. The state transition for the state machine will then be working, as demonstrated in Figure 14-10.

images

Figure 14-10. State transition of the state machine

When the CLR executes the state machine, it comes with the default value –1, which executes the default section of the switch block and updates the state with 0, sets up the continuation, and returns to the caller. The state machine value is currently set at 0 and each time the CLR tries to do another transition by calling the MoveNext method from the state machine, it will start with the state value 0. So it executes the case 0, which will break the switch block and execute the code block at Label_0104 to get the awaiter and to update the state with –1. On input of –3 as the state from the CLR, the program control transfers to Label_0196 to update the state of the state machine by –2 and sets the results to get by the caller of the state machine.

Test Cases

The following sections will explore a few common scenarios of the asynchronous method that are based on the async and await keywords.

When There Is No Return Type

When you design a program, it might require you to trigger a task and not wait for that task to finish and return to you. When you do an asynchronous operation, you can achieve this by using the FireAndForget task. To write a FireAndForget task, you need to return void instead of the Task orTask<TResult> from an async-based method. In the FireAndForget task, the C# compiler does not produce and return any task when it generates the stub method for it, therefore, it is not possible for the caller to track the completion or status of the FireAndForget-based task. Listing 14-32 shows an example of the FireAndForget task using the async and await statements.

Listing 14-32. Example of the Void Return from the Async Method

using System;
using System.Linq;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
FireAndForget();

for (int i = 0; i < Int32.MaxValue; ++i)
{
if (i % (Int16.MaxValue*999) == 0)
Console.Write(".");
}
Console.ReadLine();
}

public static async void FireAndForget()
{
var evenNumbersTask = Task.Run(
() =>
{
Enumerable.Range(1, 5).ToList().ForEach(itemOuter =>
{
int limit = new Random().Next(Int16.MaxValue * 999);
var result = Enumerable.Range(
itemOuter, limit).Count(item =>
{
return item % 2 == 0;
});
Console.WriteLine(
"\nProcessing and processed result {0}.", result);
});
});
await evenNumbersTask;
}
}
}

This program will produce the output:

..
Processing and processed result 7689415.
Processing and processed result 335471.
..
Processing and processed result 6208074.
Processing and processed result 3476038.
.
Processing and processed result 1138061.
.............................................................

The difference between a void return and task return from the asynchronous method is that the asynchronous method that returns void cannot be awaited. If you were to add another method to Listing 14-32:

public static async void FireAndForget2()
{
await FireAndForget();
}

the C# compiler will complain and show the following errors:

'Ch14.Program.FireAndForget()' does not return a Task and cannot be awaited. Consider changing
it to return Task.

If you want to await for any asynchronous method, that asynchronous method needs to return either Task or Task<TResult>. The C# compiler–generated stub method does not return Task for the void return async method, as a result, there is nothing to wait for. So you can see that with theFireAndForget method there is nothing to wait for as the FireAndForget method does not return Task.

Let’s look at the stub method the C# compiler generates for the FireAndForget method of the Listing 14-32, as shown in Listing 14-33.

Listing 14-33. The C# Compiler–Generated Stub Method for Listing 14-32

.method public hidebysig static void FireAndForget() cil managed
{
/* code removed */
.maxstack 2
.locals init (
[0] valuetype Ch14.Program/<FireAndForget>d__6 d__,
[1] valuetype
[mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder
builder)
L_0000: ldloca.s d__

L_0002: call valuetype
[mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder
[mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::
Create()
L_0007: stfld valuetype
[mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder
Ch14.Program/<FireAndForget>d__6::<>t__builder
L_000c: ldloca.s d__

L_000e: ldc.i4.m1
L_000f: stfld int32 Ch14.Program/<FireAndForget>d__6::<>1__state
L_0014: ldloca.s d__

L_0016: ldfld valuetype
[mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder
Ch14.Program/<FireAndForget>d__6::<>t__builder
L_001b: stloc.1
L_001c: ldloca.s builder
L_001e: ldloca.s d__

/* State machine has been started */
L_0020: call instance void
[mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::
Start<valuetype Ch14.Program/<FireAndForget>d__6>(!!0&)

/* There has not been return any Task from this Stub method */
L_0025: br.s L_0027

/* This method does not return */
L_0027: ret
}

Listing 14-33 shows that the CLR will not return Task as a result, so it is not possible to get the status from the FireAndForget method. It is only used as a call and leaves everything to that calling method.

When There Are Multiple Await Statements in Asynchronous Method

Listing 14-34 shows an example of the use of multiple await statements in a program.

Listing 14-34. Multiple Await Statements

using System;
using System.Linq;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
MultipleAwait();

for (int i = 0; i < Int16.MaxValue * 8; ++i)
{
if (i / byte.MaxValue==0)
Console.Write(">");
}
Console.WriteLine("Operation is completed.");
Console.ReadLine();
}
public static async void MultipleAwait()
{
await EvenNumbers();
await EvenNumbers();
}

public static async Task EvenNumbers()
{
int limit = new Random().Next(Int16.MaxValue);
Task<int> evenNumbersTask = Task.Run(
() => Enumerable.Range(1, limit).Count(
item => item % 2 == 0));
await evenNumbersTask;
Console.WriteLine("\n" + evenNumbersTask.Result);
}
}
}

This program will produce the output:

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
11635
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
8954
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Operation is completed.

When Async in the User Interface

So far, you have seen the async method used in the console application. Listing 14-35 shows an example of the async method used in the graphic user interface (GUI) application.

Listing 14-35. Example of the GUI Async

using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Ch14_GUI
{
public partial class EvenNumberDisplayWindow : Form
{
public EvenNumberDisplayWindow()
{
InitializeComponent();
}

private void btnProcess_Click(object sender, EventArgs e)
{
ShowEvenNumbers();
}
public async Task ShowEvenNumbers()
{
int limit = new Random().Next(Int32.MaxValue);
string range = string.Format("({0},{1})", 1, limit);

Task<int> evenNumbersTask = Task.Run(
() => Enumerable.Range(1, limit).Count(item => item % 2 == 0));

int count = await evenNumbersTask;
txtEvenNumbers.Text = count.ToString();
}

private void btnCurrentTime_Click(object sender, EventArgs e)
{
txtCurrentTime.Text = DateTime.Now.ToLongTimeString();
}
}
}

This program will produce the output shown in Figure 14-11.

images

Figure 14.11. Output produced using the GUI async method

Task-Based Asynchronous Pattern

The task-based asynchronous pattern (TAP) is used to implement the asynchronous methods in the .NET Framework. It is built on the basics of the Task and Task<TResult> types. When you define a method that follows the TAP, it needs to follow some rules, for example, it has to return Task orTask<TResult> and a method marks async except for the task combinators, such as WhenAll, WhenAny. In the TAP method you can also set the cancellation to cancel while it is running using the CancellationToken, or you can get the progress status of the running task using the IProgress<T>. The following section will explore Cancellation and IProgress.

Usage of the Cancellation to Cancel a Running Task

It is common behavior for a responsive system while it is doing a long-running operation to allow cancellation of the task whenever the user wants to. For the asynchronous operation, we also expected to have the option to cancel the task whenever we want to. You can achieve this by using the cancellation flag, for example, CancellationToken, a type introduced in the System.Threading namespace of the mscorlib.dll assembly. Listing 14-36 shows the CancellationToken struct definition.

Listing 14-36. CancellationToken Struct

public struct CancellationToken
{
public static CancellationToken None {}
public bool IsCancellationRequested {}
public bool CanBeCanceled {}
public System.Threading.WaitHandle WaitHandle {}
public CancellationToken(bool canceled) {}
public CancellationTokenRegistration Register(Action callback) {}
public CancellationTokenRegistration Register
(Action callback, bool useSynchronizationContext) {}
public CancellationTokenRegistration Register(Action<object> callback,
object state) {}
public CancellationTokenRegistration Register(
Action<object> callback, object state, bool useSynchronizationContext){}
public bool Equals(CancellationToken other) {}
public override bool Equals(object other) {}
public override int GetHashCode() {}
public static bool operator ==
(CancellationToken left, CancellationToken right) {}
public static bool operator !=
(CancellationToken left, CancellationToken right) {}
public void ThrowIfCancellationRequested() {}
}

A CancellationToken is created through a CancellationTokenSource, which is also defined in the System.Threading namespace of the mscorlib.dll assembly. Listing 14-37 shows the definition of the CancellationTokenSource struct.

Listing 14-37. CancellationTokenSource Struct

public class CancellationTokenSource : IDisposable
{
static CancellationTokenSource() {}
public CancellationTokenSource() {}
public CancellationTokenSource(int millisecondsDelay) {}
public CancellationTokenSource(TimeSpan delay) {}
public void Cancel() {}
public void Cancel(bool throwOnFirstException) {}
public void CancelAfter(int millisecondsDelay) {}
public void CancelAfter(TimeSpan delay) {}
public static CancellationTokenSource
CreateLinkedTokenSource(params CancellationToken[] tokens) {}
public static CancellationTokenSource
CreateLinkedTokenSource(CancellationToken token1,
CancellationToken token2) {}
public void Dispose() {}
public bool IsCancellationRequested {}
public CancellationToken Token {}
}

The source’s Token property returns the CancellationToken, which can be used to signal when the source’s Cancel method is invoked, as shown in Listing 14-38.

Listing 14-38. Example of the Task Cancellation in the Asynchronous Method

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
CancellationTokenSource cancelSource = new CancellationTokenSource();

/* Initialize the Task with the cancel Token from
* the CancellationTokenSource */
Task showNumber = ShowEvenNumbers(cancelSource.Token);

/* Following for-loop simulates as something else is going on */
for (int i = 0; i < Int64.MaxValue; ++i)
if (i == byte.MaxValue)
{
/* Call the Cancel method to cancel the task in sometime
* when the Task was executing it's Task*/
cancelSource.Cancel();
break;
}
Console.WriteLine("Cancel");
Console.ReadLine();
}

public static async Task ShowEvenNumbers(CancellationToken cancelToken)
{
int limit = new Random().Next(Int32.MaxValue);
string range = string.Format("({0},{1})", 1, limit);
/* Pass the cancel token to the Task */
Task<int> evenNumbersTask = Task.Run(
() => Enumerable.Range(1, limit).Count(
item => item % 2 == 0), cancelToken);
int count = await evenNumbersTask;
}
}
}

This program will produce the output:

Cancel

Usage of the IProgress<T> and Progress to Show Progress of a Running Task

When you use the asynchronous method, it is helpful to be able to view the progress status of the operation. To achieve this, you can use the Report method of the Progress<T> class by passing an Action delegate that fires whenever progress changes in the task. The Progress<T> class implementsIProgress<T>, which is defined in the System namespace of the mscorlib.dll assembly. Listing 14-39 shows the definition of the IProgress<T> and Progress<T> classes.

Listing 14-39. Usage of the IProgress<in T> and Progress<T>

public interface IProgress<in T>
{
void Report(T value);
}

public class Progress<T> : IProgress<T>
{
public event EventHandler<T> ProgressChanged {}
public Progress() {}
public Progress(Action<T> handler) {}
protected virtual void OnReport(T value) {}
void IProgress<T>.Report(T value) {}
}

Listing 14-40 shows an example of the progress report.

Listing 14-40. Example of the Progress Report

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
CancellationTokenSource cancelSource = new
CancellationTokenSource();
Progress<int> progressReport = new Progress<int>((status) =>
{
Console.Clear();
Console.WriteLine(status + " %");
});
Task showNumber = ShowEvenNumbers(cancelSource.Token,
progressReport);

for (int i = 0; i < Int64.MaxValue; ++i)
if (i == Int32.MaxValue)
{
cancelSource.Cancel();
break;
}
Console.WriteLine("Cancel");
Console.ReadLine();
}

public static async Task ShowEvenNumbers(
CancellationToken cancelToken,
IProgress<int> onProgressChanged)
{
int limit = new Random().Next(Int32.MaxValue);
string range = string.Format("({0},{1})", 1, limit);

Task<int> evenNumbersTask = Task.Run(
() => Enumerable.Range(1, limit).Count(item =>
{
onProgressChanged.Report((item * 100) / limit);
return item % 2 == 0;
}), cancelToken);
int count = await evenNumbersTask;
}
}
}

Combinators

In task-based asynchronous patterns, combinators refers to the creation, manipulation, or combination of tasks, for example, WhenAll, WhenAny, or Delay. The following sections will explore these combinators.

Task WhenAll

The WhenAll method is used to wait asynchronously for a task that represents multiple asynchronous operations. This method returns a task that completes when all of the tasks assigned to this method have been completed. The WhenAll method has the following overloaded signatures:

public static Task WhenAll(IEnumerable<Task> tasks) {}
public static Task<TResult[]> WhenAll<TResult>(
IEnumerable<Task<TResult>> tasks) {}
public static Task WhenAll(params Task[] tasks) {}
public static Task<TResult[]> WhenAll<TResult>(params Task<TResult>[] tasks){}

Listing 14-41 demonstrates the use of the WhenAll method.

Listing 14-41. Example of the WhenAll Method

using System;
using System.Linq;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
Task<int> combinedResult = TestWhenAll();
while (true)
{
if (combinedResult.IsCompleted)
{
Console.WriteLine("Finished : {0}", combinedResult.Result);
break;
}
Console.Write(".");
}
Console.ReadLine();
}

public static async Task<int> TestWhenAll()
{
int[] combinedResult =
await Task.WhenAll(CountEvenNumbers(),
CountEvenNumbers(), CountEvenNumbers());
return combinedResult.Sum();
}

public static async Task<int> CountEvenNumbers()
{
return await Task.Run(
() => Enumerable.Range(1, Int16.MaxValue).Count(x => x % 2 == 0));
}
}
}

This program will produce output:

..................Finished : 49149

If any exception occurred in the above example code, such as if one of the methods throws an exception, the CLR will continue with other tasks assigned in the WhenAll method and, when finished, return to the task with the related exceptions in the InnerException property of the Exception.Listing 14-42 shows an example when an exception(s) occurred in the task assigned to the WhenAll method.

Listing 14-42. Exception in the WhenAll Method

using System;
using System.Linq;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
Task<int> combinedResult = TestWhenAll();
while (true)
{
if (combinedResult.IsCompleted)
{
Console.WriteLine("Finished : {0}", combinedResult.Result);
break;
}
Console.Write(".");
}
Console.ReadLine();
}

public static async Task<int> TestWhenAll()
{
int[] combinedResult =
await Task.WhenAll(CountEvenNumbers(),
ThrowAnException(), CountEvenNumbers());
return combinedResult.Sum();
}

public static async Task<int> ThrowAnException()
{
return await Task.Run(() =>
{
throw new Exception(
"There is something wrong in the processing....");
return Enumerable.Range(1, Int16.MaxValue).Count(
x => x % 2 == 0);
});
}
public static async Task<int> CountEvenNumbers()
{
return await Task.Run(() =>
{
int result = Enumerable.Range(1, Int16.MaxValue).Count(
x => x % 2 == 0);
Console.WriteLine(result);
return result;
});
}
}
}

This will produce the output:

16383
16383
...
Unhandled Exception: System.AggregateException: One or more errors occurred. ---
> System.Exception: There is something wrong in the processing....
at Ch14.Program.<ThrowAnException>b__4()

/* Rest of the error details removed */

This output shows that the CLR process with the CountEvenNumbers methods returns the result as 16383 and 16383. On the other hand, while processing the ThrowAnException method, it throws an exception that the CLR passes as AggregateException.

Task WhenAny

The WhenAny method is used when any one of the methods (assigned to the WhenAny method) from the assigned method completes its operation. The WhenAny method has the following overloaded signatures:

public static Task<Task> WhenAny(IEnumerable<Task> tasks) {}
public static Task<Task<TResult>> WhenAny<TResult>(
IEnumerable<Task<TResult>> tasks) {}
public static Task<Task> WhenAny(params Task[] tasks) {}
public static Task<Task<TResult>> WhenAny<TResult>(
params Task<TResult>[] tasks) {}

Listing 14-43 demonstrates the use of the WhenAny method.

Listing 14-43. Example of the WhenAny Method

using System;
using System.Linq;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
Task<int> result = TestWhenAny();
while (true)
{
if (result.IsCompleted)
{
Console.WriteLine("Finished : {0}", result.Result);
break;
}
Console.Write(".");
}
Console.ReadLine();
}

public static async Task<int> TestWhenAny()
{
Task<int> firstCompleted=
await Task.WhenAny(CountEvenNumbers(),
CountEvenNumbers(), CountEvenNumbers());
return firstCompleted.Result;
}

public static async Task<int> CountEvenNumbers()
{
return await Task.Run(() => Enumerable.Range(1,
Int16.MaxValue).Count(x => x % 2 == 0));
}
}
}

This program produced the output:

................................................................................................
...........................................................................Finished : 16383

Task Delay

The Delay method from the Task class is used to set the pauses into an asynchronous method while it is executing. This method can be used to create a task that will complete after a time delay. The Delay method has the following overloaded signatures:

public static Task Delay(int millisecondsDelay) {}
public static Task Delay(TimeSpan delay) {}
public static Task Delay(int millisecondsDelay,
System.Threading.CancellationToken cancellationToken) {}
public static Task Delay(TimeSpan delay,
System.Threading.CancellationToken cancellationToken) {}

Listing 14-44 demonstrates the use of the WhenAny method.

Listing 14-44. Example of the Delay

using System;
using System.Threading.Tasks;

namespace Ch14
{
class Program
{
static void Main()
{
Task combinedResult = TestDelayWithWhenAny();

while (true)
{
if (combinedResult.IsCompleted)
{
Console.WriteLine("Finished waiting");
break;
}
Console.Write(".");
}
Console.ReadLine();
}

public static async Task TestDelayWithWhenAny()
{
await Task.WhenAny(Task.Delay(1), Task.Delay(2000));
}
}
}

This program produced the output.

................................................................................................
........................................................Finished waiting

Summary

In this chapter we have explored asynchronous programming using the Task class that has been introduced in the .NET Framework. You have learned how to set up the continuation in the Task class, how to set different options when instantiating the Task class, and how to set different continuation options while setting up the continuation in the Task. You have also learned about exception handling while doing asynchronous operation using the Task class.

The keywords async and await were introduced to show how you do asynchronous operation using these new features. Then async and await were explore in depth to learn how the C# compiler generates the stub method and state machine to handle the asynchronous operation by scheduling the task to execute the asynchronous operation using the operating system’s available schedule instead of blocking the execution of the program. You also learned how these new features improve asynchronous programming by handling the set up in the continuation block, keeping the flow and structure of the program simple.

Finally, this chapter introduced the Task-based asynchronous pattern to show the different built-in combinators you can use in asynchronous programming as well as how to set up the cancellation and progress reports of a task in .NET using the C#. The final chapter will look at diagnostic tool in .NET for debugging.