Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

The Asynchronous Programming Models (C# 5.0 Series)

, 16 Nov 2010 Ms-PL
Rate this:
Please Sign up or sign in to vote.
In previous article, I mentioned a new feature of C# 5.0 – the async and the await keywords. They are syntactical sugars that simplifies the construction of asynchronous operations code. When the C# compiler sees an await expression, it generates code that automatically invokes the expression asynch

In previous article, I mentioned a new feature of C# 5.0 – the async and the await keywords. They are syntactical sugars that simplifies the construction of asynchronous operations code. When the C# compiler sees an await expression, it generates code that automatically invokes the expression asynchronously, then immediately return the control flow to the caller so the caller code can continue executing without block; after the asynchronous operation finished, the control flow will be forwarded to the code below the await expression and execute the code sequentially till an exit criteria is reached (the exit criteria may be: the end of a method, or an iteration of a loop, etc). I emphasize that the await keyword is only a  syntactical sugar, it is therefore an alternative that compiler generates the equivalent code rather than you manually write it. Before you can understand what the C# 5.0 does for you for async and await keywords, you should first understand how the Microsoft .NET Framework provides the asynchronous programming models (APM).

In .NET Framework, there are many ways to implement an asynchronous operation: by using thread, thread pool, BeginXxx and EndXxx methods, event based APM, or Task based APM. The first way, using thread is not recommended because creating a thread is very expensive*, and it requires many manual controls to work well, so I will skip this discussion; the second way, using thread pool, is the easiest and the most commonly used way to go; the BeginXxx and EndXxx methods declared in specified types provide the standard way to perform an asynchronous operation; the event based asynchronous programming model is less popular than BeginXxx and EndXxx methods, .NET Framework just provides a very small set of the types that support event based APM; the last one, Task based APM, is introduced in .NET Framework 4 and is a part of Task Parallel Library (TPL), it dispatches asynchronous operations based on a task scheduler, it also offers many features to extend the task parallelism. The default task scheduler is implemented by using thread pool, .NET Framework also provides task schedulers implemented by Synchronization Contexts, in addition, you can implement your own task schedulers and use it to work with tasks.

* Creating a thread needs about 1.5 MB memory space, Windows will also create many additional data structures to work with this thread, such as a Thread Environment Block (TEB), a user mode stack, and a kernel mode stack. Bringing new thread may also need thread context switching, which also hurts performance. So avoid creating additional threads as much as possible.

In this article, I will go through the different ways to perform asynchronous operation, and show examples to guide you to use both of them.

The Thread Pool APM

When you want to perform an asynchronous operation, it is easy to use thread pool to do so, by calling System.Threading.ThreadPool’s QueueUserWorkItem static method, passing an instance of WaitCallback delegate and optionally an instance of Object that represents the additional parameter to associate with the instance of WaitCallback. The following example shows how to use thread pool to queue asynchronous operations.

<span style="color: blue">using</span> System;
<span style="color: blue">using</span> System.Collections.Generic;
<span style="color: blue">using</span> System.Linq;
<span style="color: blue">using</span> System.Text;
<span style="color: blue">using</span> System.Threading;
 
<span style="color: blue">namespace</span> ApmDemo
{
    <span style="color: blue">class</span> <span style="color: #2b91af">Program</span>
    {
        <span style="color: blue">static</span> <span style="color: blue">void</span> Main(<span style="color: blue">string</span>[] args)
        {
            <span style="color: green">// Define a WaitCallback instance.</span>
            WaitCallback writeCallback = state => <span style="color: #2b91af">Console</span>.WriteLine(state);
 
            <span style="color: green">// Queue user work items with ThreadPool.</span>
            <span style="color: #2b91af">ThreadPool</span>.QueueUserWorkItem(writeCallback, <span style="color: #a31515">"This is the first line"</span>);
            <span style="color: #2b91af">ThreadPool</span>.QueueUserWorkItem(writeCallback, <span style="color: #a31515">"This is the second line"</span>);
            <span style="color: #2b91af">ThreadPool</span>.QueueUserWorkItem(writeCallback, <span style="color: #a31515">"This is the third line"</span>);
 
            <span style="color: #2b91af">Console</span>.ReadKey();
        }
    }
}

In the above example, I initialized an instance of a WaitCallback instance by assigning a lambda expression as the delegate body, then called ThreadPool’s static method QueueUserWorkItem, passed this instance as the first parameter, and a string as its second parameter. When calling this method, the thread pool seeks for a free thread in the pool, associates the instance of the WaitCallback delegate to that thread, and dispatches this thread to execute the delegate at some time; if there is no free thread in the pool, the thread pool creates a new thread, associates the delegate instance, and then dispatches to execute at some time. I queued three user work items to the thread pool, by calling QueueUserWorkItem method for three times.

When I try to run this program, I may get the following output:

This is the first line
This is the second line
This is the third line

But sometimes I also get the following output:

This is the second line
This is the first line
This is the third line

Please note that the executing order of the queued user work items is unpredictable because there is no way to know when a thread in the thread pool is scheduled to execute the code. As shown above, the work items may complete in sequential, and it is also possible that the work items complete in reverse order. Therefore, do not write asynchronous code that relies on the execution order.

I highly recommended that you use the thread pool APM as much as possible, here are some reasons:

  1. Thread pool is managed automatically by the CLR. When you queue a user item to the thread pool, you never care which thread it will be associated and when it will be executed; the CLR handles everything for you – this pattern enables you to write easy-to-read,. straightforward and less buggy code.
  2. Thread pool manages threads wisely. When perform an asynchronous operation, CLR requires additional thread to perform this operation so the operation can take without blocking the current thread, but however, creating new thread is expensive, introducing new thread every time to serve a user work item is heavy and waste of resources. Thread pool manages a set of threads initially, when a user work item is queued, the thread pool adds this work item to a global work item list, then a CLR thread will check this global work item list, if it is not empty, this thread picks up a work item, and dedicates it to a free thread in the pool; if there is no free thread, the thread pool will then create a new thread, and dedicate it to this newly created thread. The thread pool always chooses to use as less thread as possible to serve all queued user work items. Hence, by using thread pool, CLR uses less system resources, makes the asynchronous operations scheduling effective and efficient.
  3. Thread pool has better performance. Thread pool mechanism guarantees that it can use maximum or configured CPU resources to server user work items. If you are running your program in a multi-core CPU environment, the thread pool initially creates threads which number is equal to the number of the installed CPUs in that environment; when scheduling a user work item, thread pool automatically balances the threads, and makes sure that every logical CPU core is used to serve the work items. This brings a flexibility to dispatch CPU resources and also helps to improve the whole system performance.

Though there are a lot of benefits using thread pool, there are also limits:

  1. Thread pool queues a user work item, and executes it at an uncertain time, when it finished processing a user item, there is no way for the caller code to know when it will complete, thus it is very difficult to write continuation code after this work item is completed. Specially, some operations, like read a number of bytes from a file stream, must get an notification when the operation is completed asynchronously, then the caller code can determine how many bytes it read from the file stream, and use these bytes to do other things.
  2. The ThreadPool’s QueueUserWorkItem method only takes a delegate that receives one parameter, if you code is designed to process more than one parameter, it is impossible to directly pass all the parameters to this method; instead, you may create additional data structure to wrap those parameter, then alternatively pass the wrapper type instance to the method. This reduces the readability and maintainability of your code.

To solve these problems, you may use the following standard way to perform asynchronous operations.

The Standard APM

The Framework Class Library (FCL) ships various types that have BeginXxx and EndXxx methods, these methods are designed to perform asynchronous operations. For example, the System.IO.FileStream type defines Read, BeginRead and EndRead methods, Read method is a synchronous method, it reads a number of bytes from a file stream synchronously; in other word, it won’t return until the read operation from the file stream is completed. The BeginRead and EndRead methods are pair, when calling BeginRead method, CLR queues this operation to the hardware device (in this case, the hard disk), and immediately return the control flow to the next line of code and then continue to execute; when the asynchronous read operation is completed by the hardware device, the device notifies the Windows kernel that the operation is completed, then the Windows kernel notifies CLR to execute a delegate which is specified as a parameter by calling BeginRead method, in this delegate, the code must call EndRead method so that the CLR can transit the number of bytes read from the buffer to the calling delegate, then the code can access the bytes read from the file stream.

here is what the Read, BeginRead and EndRead method signatures are defined.

<span style="color: blue">public</span> <span style="color: blue">override</span> <span style="color: #2b91af">IAsyncResult</span> BeginRead(<span style="color: blue">byte</span>[] array, <span style="color: blue">int</span> offset, 
    <span style="color: blue">int</span> numBytes, AsyncCallback userCallback, <span style="color: blue">object</span> stateObject);
<span style="color: blue">public</span> <span style="color: blue">override</span> <span style="color: blue">int</span> EndRead(<span style="color: #2b91af">IAsyncResult</span> asyncResult);
<span style="color: blue">public</span> <span style="color: blue">override</span> <span style="color: blue">int</span> Read(<span style="color: blue">byte</span>[] array, <span style="color: blue">int</span> offset, <span style="color: blue">int</span> count);

Usually, The BeginXxx method have the same parameters with the Xxx method and two additional parameters: userCallback and stateObject. The userCallback is of type AsyncCallback, which takes one parameter of type IAsyncResult which brings additional information to this asynchronous operation; the stateObject parameter is the instance that you want to pass to the userCallback delegate, which can be accessed by AsyncState property defined on this delegate’s asyncResult argument.

The following code demonstrates how to use BeginXxx and EndXxx methods to perform asynchronous operations.

<span style="color: blue">using</span> System;
<span style="color: blue">using</span> System.Collections.Generic;
<span style="color: blue">using</span> System.Linq;
<span style="color: blue">using</span> System.Text;
<span style="color: blue">using</span> System.Threading;
<span style="color: blue">using</span> System.IO;
 
<span style="color: blue">namespace</span> ApmDemo
{
    <span style="color: blue">internal</span> <span style="color: blue">class</span> <span style="color: #2b91af">Program</span>
    {
        <span style="color: blue">private</span> <span style="color: blue">const</span> <span style="color: blue">string</span> FilePath = <span style="color: #a31515">@"c:\demo.dat"</span>;
 
        <span style="color: blue">private</span> <span style="color: blue">static</span> <span style="color: blue">void</span> Main(<span style="color: blue">string</span>[] args)
        {
            <span style="color: green">// Test async write bytes to the file stream.</span>
            <span style="color: #2b91af">Program</span>.TestWrite();
 
            <span style="color: green">// Wait operations to complete.</span>
            <span style="color: #2b91af">Thread</span>.Sleep(60000);
        }
 
        <span style="color: blue">private</span> <span style="color: blue">static</span> <span style="color: blue">void</span> TestWrite()
        { 
            <span style="color: green">// Must specify FileOptions.Asynchronous otherwise the BeginXxx/EndXxx methods are</span>
            <span style="color: green">// handled synchronously.</span>
            <span style="color: #2b91af">FileStream</span> fs = <span style="color: blue">new</span> <span style="color: #2b91af">FileStream</span>(<span style="color: #2b91af">Program</span>.FilePath, <span style="color: #2b91af">FileMode</span>.OpenOrCreate,
                <span style="color: #2b91af">FileAccess</span>.Write, <span style="color: #2b91af">FileShare</span>.None, 8, <span style="color: #2b91af">FileOptions</span>.Asynchronous);
 
            <span style="color: blue">string</span> content = <span style="color: #a31515">"A quick brown fox jumps over the lazy dog"</span>;
            <span style="color: blue">byte</span>[] data = <span style="color: #2b91af">Encoding</span>.Unicode.GetBytes(content);
 
            <span style="color: green">// Begins to write content to the file stream.</span>
            <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Begin to write"</span>);
            fs.BeginWrite(data, 0, data.Length, <span style="color: #2b91af">Program</span>.OnWriteCompleted, fs);
            <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Write queued"</span>);
        }
 
        <span style="color: blue">private</span> <span style="color: blue">static</span> <span style="color: blue">void</span> OnWriteCompleted(<span style="color: #2b91af">IAsyncResult</span> asyncResult)
        { 
            <span style="color: green">// End the async operation.</span>
            <span style="color: #2b91af">FileStream</span> fs = (<span style="color: #2b91af">FileStream</span>)asyncResult.AsyncState;
            fs.EndWrite(asyncResult);
 
            <span style="color: green">// Close the file stream.</span>
            fs.Close();
            <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Write completed"</span>);
 
            <span style="color: green">// Test async read bytes from the file stream.</span>
            <span style="color: #2b91af">Program</span>.TestRead();
        }
 
        <span style="color: blue">private</span> <span style="color: blue">static</span> <span style="color: blue">void</span> TestRead()
        {
            <span style="color: green">// Must specify FileOptions.Asynchronous otherwise the BeginXxx/EndXxx methods are</span>
            <span style="color: green">// handled synchronously.</span>
            <span style="color: #2b91af">FileStream</span> fs = <span style="color: blue">new</span> <span style="color: #2b91af">FileStream</span>(<span style="color: #2b91af">Program</span>.FilePath, <span style="color: #2b91af">FileMode</span>.OpenOrCreate,
                <span style="color: #2b91af">FileAccess</span>.Read, <span style="color: #2b91af">FileShare</span>.None, 8, <span style="color: #2b91af">FileOptions</span>.Asynchronous);
 
            <span style="color: blue">byte</span>[] data = <span style="color: blue">new</span> <span style="color: blue">byte</span>[1024];
 
            <span style="color: green">// Begins to read content to the file stream.</span>
            <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Begin to read"</span>);
            <span style="color: green">// Pass both Fs and data as async state object.</span>
            fs.BeginRead(data, 0, data.Length, <span style="color: #2b91af">Program</span>.OnReadCompleted, <span style="color: blue">new</span> { Stream = fs, Data = data });
            <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Read queued"</span>);
        }
 
        <span style="color: blue">private</span> <span style="color: blue">static</span> <span style="color: blue">void</span> OnReadCompleted(<span style="color: #2b91af">IAsyncResult</span> asyncResult)
        {
            <span style="color: blue">dynamic</span> state = asyncResult.AsyncState;
 
            <span style="color: green">// End read.</span>
            <span style="color: blue">int</span> bytesRead = state.Stream.EndRead(asyncResult);
 
            <span style="color: green">// Get content.</span>
            <span style="color: blue">byte</span>[] data = state.Data;
            <span style="color: blue">string</span> content = <span style="color: #2b91af">Encoding</span>.Unicode.GetString(data, 0, bytesRead);
            
            <span style="color: green">// Display content and close stream.</span>
            <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Read completed. Content is: {0}"</span>, content);
            state.Stream.Close();
            <span style="color: #2b91af">Console</span>.ReadKey();
        }
    }
}

This program tests asynchronous read/write operations from/to a specified file stream, by using BeginRead, EndRead, BeginWrite and EndWrite methods defined on System.IO.FileStream class. When I try to run this program, I get the following output:

image

Now you may already know how to use the standard way to perform an asynchronous operation by calling BeginXxx and EndXxx methods. In fact, this standard way supports many more features as I demonstrated here, such as cancellation, which I will discuss in later articles, and supporting cancellation is really a big plus of this pattern. By using this pattern, you can solve some problems I listed for the thread pool APM, and you can also have additional benefits which I summarize below.

  1. Supports continuation. When an asynchronous operation is completed, the userCallback delegate is invoked, so the caller code can perform other operations based on the result of this asynchronous operation.
  2. Supports I/O based asynchronous operations. The standard APM works with kernel objects to perform I/O based asynchronous operations. When an I/O based asynchronous operation is requested by calling the BeginXxx method, the CLR doesn’t introduce new thread pool thread to dedicate this task, instead, it uses a Windows kernel object to wait for the hardware I/O device to return (through its driver software) when it finishes the task. CLR just uses the hardware device drivers and kernel objects to perform I/O based asynchronous operations, no more managed resources are used to handle this case. hence, it actually improves the system performance by releasing CPU time slices and threads usage.
  3. Supports cancellation. When an asynchronous operation is triggered, user may cancel this operation by calling System.Threading.CancellationTokenSource’s Cancel method, I will introduce this class in the later articles.

But however, by using standard APM, your code becomes more complicated. That’s because all the task continuation happen outside of the calling context, for example, in the above read/write file stream example, the OnReadCompleted and the OnWriteCompleted are separate methods and invoked by a different thread than the current calling thread, this behavior may confuse developers, and therefore make your code logic not clear to understand.

Note: The async method and the await expressions bring a clear, logical and organized code structure to the asynchronous programming.

The Event-based APM

The Framework Class Library (FCL) also ships with some types that support event-based APM. For example, the System.Net.WebClient class defines a DownloadDataAsync method, and a DownloadDataCompleted event, by calling DownloadDataAsync method, CLR begins an asynchronous operation for downloading the data from a specified URL, when it is completed, the DownloadDataCompleted event will be fired, the argument e of type System.Net.DownloadDataCompletedEventArgs contains results and additional information of this operation. Here is the code demonstrates how to use event based APM to perform asynchronous operation.

<span style="color: blue">using</span> System;
<span style="color: blue">using</span> System.Collections.Generic;
<span style="color: blue">using</span> System.Linq;
<span style="color: blue">using</span> System.Text;
<span style="color: blue">using</span> System.Threading;
<span style="color: blue">using</span> System.IO;
<span style="color: blue">using</span> System.Net;
 
<span style="color: blue">namespace</span> ApmDemo
{
    <span style="color: blue">internal</span> <span style="color: blue">class</span> <span style="color: #2b91af">Program</span>
    {
        <span style="color: blue">private</span> <span style="color: blue">static</span> <span style="color: blue">void</span> Main(<span style="color: blue">string</span>[] args)
        {
            <span style="color: #2b91af">WebClient</span> wc = <span style="color: blue">new</span> <span style="color: #2b91af">WebClient</span>();
            wc.DownloadDataAsync(<span style="color: blue">new</span> <span style="color: #2b91af">Uri</span>(<span style="color: #a31515">"http://www.markzhou.com"</span>));
            wc.DownloadDataCompleted += (s, e) => <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #2b91af">Encoding</span>.UTF8.GetString(e.Result));
 
            <span style="color: #2b91af">Console</span>.ReadKey();
        }
    }
}

Actually it acts with the same effect as using BeginXxx and EndXxx methods, the difference is event-based APM is more close to the object model layer, you can ever use a designer and a property window to drag-drop the component to the user interface and then set the event handler through the property window, as opposed, standard APM doesn’t provide events to subscribe, this helps to improve the system performance because implementing events may require additional system resources.

There are very small set of types in FCL support event-based APM, personally, I suggest not use this pattern as much as possible, the event-based APM may suit for the application developers because they are component consumers,. not the component designers, and the designer supportability is not mandatory for the component designers (library developers).

The Task-based APM

Microsoft .NET Framework 4.0 introduces new Task Parallel Library (TPL) for parallel computing and asynchronous programming. The mainly used Task class, which defined in System.Threading.Tasks namespace, represents a user work item to complete, to use task based APM, you have to create a new instance of Task, or Task<T> class, passing an instance of Action  or Action<T> delegate as the first parameter of the constructor of Task or Task<T>, then, call the Task’s instance method Start, notifies the task scheduler to schedule this task as soon as possible.

The following code shows how to use task based APM to perform a compute-bound asynchronous operation.

<span style="color: blue">using</span> System;
<span style="color: blue">using</span> System.Collections.Generic;
<span style="color: blue">using</span> System.Linq;
<span style="color: blue">using</span> System.Text;
<span style="color: blue">using</span> System.Threading.Tasks;
 
<span style="color: blue">namespace</span> Demo
{
    <span style="color: blue">class</span> <span style="color: #2b91af">Program</span>
    {
        <span style="color: blue">static</span> <span style="color: blue">void</span> Main(<span style="color: blue">string</span>[] args)
        {
            <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Task based APM demo"</span>);
            <span style="color: #2b91af">Task</span> t = <span style="color: blue">new</span> <span style="color: #2b91af">Task</span>(() =>
            {
                <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"This test is output asynchronously"</span>);
            });
            t.Start();
            <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Task started"</span>);
            
            <span style="color: green">// Wait task(s) to complete.</span>
            <span style="color: #2b91af">Task</span>.WaitAll(t);
        }
    }
}

If I run this program, I will get the following output:

image

Alternatively, if the task delegate returns a value, you can use Task<T> instead of Task, after the task is complete, you can query the result by Task<T>’s Result property. The following code shows how to use Task<T> to calculate the nth exponent to 2 (n is positive only).

<span style="color: blue">using</span> System;
<span style="color: blue">using</span> System.Collections.Generic;
<span style="color: blue">using</span> System.Linq;
<span style="color: blue">using</span> System.Text;
<span style="color: blue">using</span> System.Threading.Tasks;
 
<span style="color: blue">namespace</span> Demo
{
    <span style="color: blue">class</span> <span style="color: #2b91af">Program</span>
    {
        <span style="color: blue">static</span> <span style="color: blue">void</span> Main(<span style="color: blue">string</span>[] args)
        {
            <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Task based APM demo"</span>);
            Func<<span style="color: blue">int</span>, <span style="color: blue">int</span>> calc = (n) => { <span style="color: blue">return</span> 2 << (n - 1); };
 
            <span style="color: #2b91af">Task</span><<span style="color: blue">int</span>> t = <span style="color: blue">new</span> <span style="color: #2b91af">Task</span><<span style="color: blue">int</span>>(() =>
            {
                <span style="color: blue">return</span> calc(10);
            });
 
            t.Start();
            <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Task started"</span>);
            
            <span style="color: green">// Wait task(s) to complete.</span>
            <span style="color: green">// After t is complete, get the result.</span>
            <span style="color: #2b91af">Task</span>.WaitAll(t);
            <span style="color: #2b91af">Console</span>.WriteLine(t.Result);
        }
    }
}

When I run this program, I get the following output:

image

The Task’s static method WaitAll waits all tasks specified in the parameter array synchronously, meaning that the current thread will be blocked till all the specified tasks are complete. If you don’t want to block the current thread, and you intend to do something after a certain task is complete, you may use the Task’s instance method ContinueWith, Here shows how to do continuation tasks in the following code.

<span style="color: blue">using</span> System;
<span style="color: blue">using</span> System.Collections.Generic;
<span style="color: blue">using</span> System.Linq;
<span style="color: blue">using</span> System.Text;
<span style="color: blue">using</span> System.Threading.Tasks;
 
<span style="color: blue">namespace</span> Demo
{
    <span style="color: blue">class</span> <span style="color: #2b91af">Program</span>
    {
        <span style="color: blue">static</span> <span style="color: blue">void</span> Main(<span style="color: blue">string</span>[] args)
        {
            <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Task based APM demo"</span>);
            Func<<span style="color: blue">int</span>, <span style="color: blue">int</span>> calc = (n) => { <span style="color: blue">return</span> 2 << (n - 1); };
 
            <span style="color: #2b91af">Task</span><<span style="color: blue">int</span>> t = <span style="color: blue">new</span> <span style="color: #2b91af">Task</span><<span style="color: blue">int</span>>(() =>
            {
                <span style="color: blue">return</span> calc(10);
            });
            
            <span style="color: green">// Set a continuation operation.</span>
            t.ContinueWith(task => { <span style="color: #2b91af">Console</span>.WriteLine(task.Result); <span style="color: blue">return</span> task.Result; });
            t.Start();
            <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Task started"</span>);
            
            <span style="color: green">// Wait for a user input to exit the program.</span>
            <span style="color: #2b91af">Console</span>.ReadKey();
        }
    }
}

The task based APM has many features, I list some of the important features below:

  1. You can specify TaskCreationOptions when creating a task, indicating how the task scheduler will schedule the task.
  2. You can specify a CancellationTokenSource when creating a task, indicating the associated cancellation token used to cancel a task.
  3. You can use ContinueWith, or ContinueWith<T> method to perform continuation tasks.
  4. You can wait all specified tasks to complete synchronously by calling Task’s static WaitAll method, or wait any of the tasks to complete synchronously by calling Task’s static WaitAny method.
  5. If you want to create a bunch of tasks with the same creation/continuation settings, you can use TaskFactory’s instance StartNew method.
  6. The task based APM requires a task scheduler to work, the default task scheduler is implemented on top of the thread pool, however, you may change the task scheduler associated with a task to a synchronization context task scheduler, or a customized task scheduler.
  7. You can easily convert a BeginXxx and EndXxx pattern asynchronous operation into a task based APM by calling TaskFactory’s instance FromAsync or FromAsync<T> method.

Task, async method and await expression

I would like to point out that the async method and the await expression/statement in C# 5.0 are implemented in the compiler level by building on top of the task based APM. An async method must have either a return type of void, or a return type of Task or Task<T>, this limitation is obvious because if there is no await expression in the async method, this method will be invoked synchronously; thus this method can be treated as a normal method, making a void return value is clear; otherwise, if the async method contains at least one await expression, this method will be invoked asynchronously and because of await expressions based on the task based APM, a Task or a Task<T> instance must be returned from this method to enable another await expression to perform on this method.

To make this clear, I modify the code to calculate the nth exponent to 2 by using async and await, see the following:

<span style="color: blue">using</span> System;
<span style="color: blue">using</span> System.Collections.Generic;
<span style="color: blue">using</span> System.Linq;
<span style="color: blue">using</span> System.Text;
<span style="color: blue">using</span> System.Threading;
<span style="color: blue">using</span> System.Threading.Tasks;
 
<span style="color: blue">namespace</span> Demo
{
    <span style="color: blue">class</span> <span style="color: #2b91af">Program</span>
    {
        <span style="color: blue">static</span> <span style="color: blue">void</span> Main(<span style="color: blue">string</span>[] args)
        {
            <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Task based APM demo"</span>);
            
            <span style="color: green">// Call Exponnent() asynchronously.</span>
            <span style="color: green">// And immediately return the control flow.</span>
            <span style="color: green">// If I don't put a Task here, the program will sometimes</span>
            <span style="color: green">// terminate immediately.</span>
            <span style="color: #2b91af">Task</span> t = <span style="color: blue">new</span> <span style="color: #2b91af">Task</span>(<span style="color: blue">async</span> () =>
            {
                <span style="color: blue">int</span> result = <span style="color: blue">await</span> <span style="color: #2b91af">Program</span>.Exponent(10);
 
                <span style="color: green">// After the operation is completed, the control flow will go here.</span>
                <span style="color: #2b91af">Console</span>.WriteLine(result);
            });
 
            t.Start();
            <span style="color: #2b91af">Console</span>.ReadKey();
        }
 
        <span style="color: blue">static</span> <span style="color: blue">async</span> <span style="color: #2b91af">Task</span><<span style="color: blue">int</span>> Exponent(<span style="color: blue">int</span> n)
        {
            <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Task started"</span>);
            <span style="color: blue">return</span> <span style="color: blue">await</span> <span style="color: #2b91af">TaskEx</span>.Run<<span style="color: blue">int</span>>(() => 2 << (n - 1));
        }
    }
}

When I try to run this program, I get the exact same result as the example showed in the Task based APM section.

You may still confuse the above code, concern how it works and what the exactly control flow to run this code. In the coming articles, I will discuss it in details.

Conclusion

The Microsoft .NET Framework provides many ways to perform asynchronous operations, you may choose one or some of them by investigating your case; though there are various ways, some of them are not recommended, such as using System.Threading.Thread class to implement asynchronous operations, or event-based APM. The most popular ways are using thread pool or task based APM. In addition, task based APM is used to implement async method and await expression/statement in C# 5.0.

At last, I summarize the different asynchronous models in the following table for reference.

Pattern Description Based On Notes
Thread based By creating System.Threading.Thread instance Managed Thread Expensive, not recommended
Standard BeginXxx and EndXxx methods By calling BeginXxx method with a user callback; calling EndXxx inside that user callback Thread pool Widely used, standard, recommended, support cancellation and continuation
ThreadPool By calling ThreadPool’s static QueueUserWorkItem method Thread pool Widely used, recommended use as much as possible
Delegate By calling Delegate’s BeginInvoke and EndInvoke instance methods Thread pool Less used
Event based By subscribe appropriate event and calling appropriate method Thread pool Avoid use as much as possible, not recommended
Task based By creating System.Threading.Tasks.Task instance A specified task scheduler Recommended, supports all features of a thread pool pattern, and has many other features
async method and await expression By using async and await keywords Task based pattern The new C# 5.0 asynchronous pattern

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

mazhou
Software Developer (Senior) Microsoft
Singapore Singapore
Mark is a Windows developer, he mainly works for building libraries and tools for the developers, and as well he designs and implements Windows based applications.

Comments and Discussions

 
QuestionNo code PinmemberBill Warner5-May-14 9:44 
GeneralMy vote of 2 PinmemberOleksandr Kulchytskyi24-Dec-12 0:24 
Questionyour article explanation is not properly formated PinmemberTridip Bhattacharjee5-Sep-12 21:50 
GeneralMy vote of 5 PinmemberAbinash Bishoyi28-Dec-11 2:34 
SuggestionThe point of async/await PinmemberCS-APJ23-Jun-11 2:09 
GeneralRe: The point of async/await Pinmembermazhou23-Jun-11 3:11 
GeneralMy vote of 5 PinmemberHuangxm23-Mar-11 0:44 
GeneralEvent based pattern is supposed to perform better Pinmemberkwaclaw20-Dec-10 4:39 
GeneralAnother 5 goes to... Pinmemberafaz23-Nov-10 21:00 
GeneralMy vote of 5 PinmemberJH6416-Nov-10 6:33 
GeneralMy vote of 5 PinmemberKanasz Robert11-Nov-10 22:29 
GeneralGreat article PinmemberWayne Ye11-Nov-10 18:49 
GeneralMy vote of 5 PinmemberWayne Ye11-Nov-10 18:46 
GeneralMy vote of 5 PinmemberSledgeHammer0111-Nov-10 10:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411023.1 | Last Updated 16 Nov 2010
Article Copyright 2010 by mazhou
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid