CHAPTER 9

image

Server-Side Async

In Chapter 6 we looked at asynchrony on the client side in some depth. Strong as the case is for using asynchronous execution on the client, it is even stronger on the server side. One could even say that the server side is fundamentally flawed unless request execution is handled, to some degree, asynchronously. In this chapter we will examine the reasons for implementing server-side asynchrony and the challenges it presents. Then we will analyze the asynchronous features in .NET 4.0 and 4.5 in the major server-side frameworks: ASP.NET (in a number of guises) and Windows Communication Foundation (WCF).

Natural Parallelism

The processing of requests from clients lends itself very naturally to parallel execution. Requests, by their nature, tend to be discrete and unrelated to other requests happening concurrently. So there should be no contention between clients when processing any given request (unless of course two clients try to update the same database record; but that’s an issue for the database, not for the server). You can see this model in Figure 9-1 where the requests do not interact with one another as they are processed by the server on the way to the database.

9781430259206_Fig09-01.jpg

Figure 9-1. Natural server request parallelism

In general this isolation is achieved by dedicating a thread to the request over its whole lifetime. This means that any required request-specific state can be stored in thread- or call-specific constructs, such as thread local storage and CallContext, until the request is complete. ASP.NET makes request-specific state available via an HttpContext object, which is available to code running as part of the request through the HttpContext.Current static property. WCF makes request-specific state available in a similar way, but using a class called OperationContext and a static property OperationContext.Current.HttpContext is based on CallContext and OperationContext on thread local storage.

THREAD LOCAL STORAGE AND CALLCONTEXT

Thread Local Storage (TLS) is an operating system facility that allows data to be associated with a specific thread rather than on the heap or stack. The data is stored in “slots,” which act as storage areas for data. The number of TLS slots is limited across the process (as all threads have all slots), but you are guaranteed to have at least 64 available. Data, once placed in a slot, will live for the lifetime of the thread unless overwritten or the slot itself is removed.

CallContext, on the other hand, is a .NET construct that allows state to be carried, out of band, along the logical thread of execution. In other words, if Method A calls Method B, which in turn calls Method C, then data set in the CallContext in A will be available in C without having to pass parameters down through the call chain.

The Problem of I/O

The natural parallelism model just described is ideal if the requests are CPU bound. However, if the requests perform I/O, then simply blocking a request thread while the I/O takes place means you have a thread that is consuming resources, but doing no useful work (see Figure 9-2). Inevitably, I/O-based work will, from the server’s perspective, be performed by the hardware: disk heads moving to the data on disk, the network card waiting for packets to be received from the database server. In reality, it would be better to have the I/O running in the background and allow the request thread to get on with other work. This you can achieve using overlapped (asynchronous) I/O.

9781430259206_Fig09-02.jpg

Figure 9-2. Request processing with synchronous I/O

By using overlapped I/O the request thread could perform many asynchronous pieces of I/O concurrently and then combine the results for the response message (see Figure 9-3). This is certainly one way to ensure that the thread can do more than simply block for multiple I/O requests sequentially. However, the majority of requests arguably do not need to perform multiple I/O operations to complete a single request. What you really need to do in this situation is to give up the request thread to allow it to get back to its primary job: servicing requests. You then only commit a thread once the I/O has completed and you have CPU-based work to perform once more. Windows has a feature called I/O Completion Ports (IOCP), and this is its core purpose.

9781430259206_Fig09-03.jpg

Figure 9-3. Request Processing with Overlapped I/O

The idea behind IOCP is that the I/O operations are performed overlapped but, rather than the request thread waiting for the completion, the completion gets enqueued, and then a thread from a thread pool is given the results of the I/O. This means, assuming you can correlate the completion of the I/O back to the request that caused it to be performed, the I/O thread pool can complete the request. This also means that the original request thread can get back to servicing new requests while the I/O takes place (see Figure 9-4).

9781430259206_Fig09-04.jpg

Figure 9-4. Request processing with I/O completion ports

If you need to build highly scalable server solutions, the use of IOCP is critical to reducing the resources you have to use for high throughput. As an illustration, look at the output from two load tests. The first shows the CPU load when running the load test for an ASP.NET WebForms site built using synchronous pages and I/O (Figure 9-5). You can see that the processor time is very high even though, it turns out, the vast majority of processing was waiting on a slow database query. Compare this to the second load test, which uses an asynchronous page and an asynchronous database query. Now you see, because the processing all takes place in the database, that the web server is under comparatively low load (Figure 9-6).

9781430259206_Fig09-05.jpg

Figure 9-5. Load test output for synchronous pages

9781430259206_Fig09-06.jpg

Figure 9-6. Load test output for asynchronous pages

Having seen that asynchrony can be very important on the server side, how do you go about implementing it in the four main .NET server-side technologies: ASP.NET WebForms, ASP.NET MVC, ASP.NET Web API, and WCF? If you look at the MSDN documentation for using IOCP you may suddenly feel quite daunted as it’s not a simple API. Fortunately, however, you are on the .NET platform and the complexity in the IOCP model is wrapped up nicely by the .NET Framework class library.

How the IOCP functionality is exposed depends on which version of .NET you are using. .NET 4.0 introduced the TPL and so, as on other occasions in the past, the base class library (BCL) itself was not changed to take advantage of the new feature. Typically new patterns for building APIs don’t come into heavy use until the version after the one that delivered the new functionality (generics are another example; although introduced in .NET 2.0, they didn’t really appear heavily in the BCL until 3.5). Therefore, if you look at the BCL in 4.5 you see many new methods appearing on classes to take advantage of the power of TPL. As a result, how you build asynchronous code in 4.0 and 4.5 is quite different—especially with the introduction of the async and await keywords. We shall therefore examine how to build server-side asynchronous code in both .NET 4.0 and 4.5.

ASP.NET WebForms

ASP.NET WebForms) was the first .NET web technology, and it sits on top of a common HTTP processing pipeline. It is based on the concept of a page that contains a set of server-side controls. The controls have state and that state is used, by the control, to affect the rendering of the appropriate HTML to represent the UI for that control. A page is built from markup (in an ASPX file) and a code-behind file with .NET code in it. These files are compiled, as partial classes, into a single .NET type that derives, directly or indirectly, from System.Web.UI.Page, which is a handler.

THE ASP.NET HTTP PIPELINE

The HTTP pipeline is a set of components that are assembled to process HTTP requests that are run under the control of ASP.NET. The first component to receive the request is called HttpRuntime, which is responsible for decomposing the request into an object model (HttpContext) and then creating the rest of the pipeline. The next component assembled is an HttpApplication that is responsible for application lifetime and orchestrating the execution of requests, culminating in the execution of a handler whose responsibility it is to generate the response.

HttpApplication’s orchestration is achieved by firing a series of events as the request moves through processing. For example: BeginRequest, AuthenticateRequest, PreRequestHandlerExecute, PostRequestHandlerExecute, and EndRequest. Interception code called modules subscribe to these events to perform their function such as output caching, authentication, authorization, and session control. In fact the modules can even say, “Don’t bother running the handler, I have the content here,” which is the case for the output caching module.

ASP.NET requests are executed on thread pool worker threads.

There are a series of stages in building the page, and you can get involved in any stage by overriding the appropriate virtual member of the Page class. You can see the page lifecycle in Figure 9-7. Now if all actions within the page lifecycle are synchronous, the request thread is occupied by the same request for its entire duration. So if you perform, say, a synchronous long-running database query during OnLoad, then the request thread blocks.

9781430259206_Fig09-07.jpg

Figure 9-7. The WebForms page lifecycle

It is good practice to isolate the application code from the data access to allow yourself to make changes in the data access mechanism without forcing a cascading change throughout the application. Therefore, over the course of this chapter, you will be accessing the database via a repository. Take a look at the synchronous GetAuthors method of the repository (Listing 9-1). Notice that it calls the synchronous ExecuteReader method of SqlCommand to execute the GetAuthors stored procedure. It turns out that the GetAuthors stored procedure is long running.

Listing 9-1.  Synchronous Implementation of the AuthorRepository

public class AuthorRepository
{
    private const string connStr = "Server=.;Database=pubs;Integrated Security=SSPI";
  
    public IEnumerable<Author> GetAuthors()
    {
        var authors = new List272103_1_En();
        using (var conn = new SqlConnection(connStr))
        {
            using (var cmd = new SqlCommand("GetAuthors", conn))
            {
                cmd.CommandType = CommandType.StoredProcedure;
                conn.Open();
  
                using (SqlDataReader reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        authors.Add(new Author
                            {
                                FirstName = (string)reader["au_fname"],
                                LastName = (string)reader["au_lname"]
                            });
                    }
                }
            }
        }
  
        return authors;
    }
}

A Synchronous WebForms Implementation

Before we look at asynchronous pages, we should look at a synchronous page as a baseline. Listing 9-2 and Listing 9-3 show the code for a synchronous page where a Label called output is populated by the number of records returned from the synchronous GetAuthors method of the AuthorRepository.

Listing 9-2.  ASPX File for a Synchronous Page

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="SyncPage.aspx.cs"
    Inherits="Dotnet40.SyncPage" %>
  
<!DOCTYPE html>
  
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Label runat="server" ID="output"></asp:Label>
    </div>
    </form>
</body>
</html>

Listing 9-3.  Code-Behind for a Synchronous Page

public partial class SyncPage : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
         var repo = new AuthorRepository();
  
         output.Text = repo.GetAuthors().Count().ToString();
    }
}

As stated earlier, however, the GetAuthors method of the AuthorRepository executes a very long-running stored procedure. Therefore, you could benefit greatly from using IOCP to manage the I/O and so you will make the page asynchronous. To do this you are going to need a repository that supports asynchronous execution. Initially you will need an APM version of GetAuthors (Listing 9-4). The BeginGetAuthors method now calls the APM version of ExecuteReader. Note that you need to add the Asynchronous Processing = true flag to the connection string to allow asynchronous queries to be executed on the connection.

Listing 9-4.  APM Version of GetAuthors

private const string connStr =
            "Server=.;Database=pubs;Integrated Security=SSPI; Asynchronous Processing=true";
private SqlConnection apmConn;
private SqlCommand apmCmd;
 
public IAsyncResult BeginGetAuthors(AsyncCallback callback, object state)
{
    apmConn = new SqlConnection(connStr);
    apmCmd = new SqlCommand("GetAuthors", apmConn);
  
    apmConn.Open();
    return apmCmd.BeginExecuteReader(callback, state);
}
  
public IEnumerable<Author> EndGetAuthors(IAsyncResult iar)
{
    try
    {
        var authors = new List272103_1_En();
        using (SqlDataReader reader = apmCmd.EndExecuteReader(iar))
        {
            while (reader.Read())
            {
                authors.Add(new Author
                    {
                        FirstName = (string)reader["au_fname"],
                        LastName = (string)reader["au_lname"]
                    });
                }
  
                return authors;
            }
        }
        finally
        {
            apmCmd.Dispose();
            apmConn.Dispose();
        }
    }
}

Asynchronous Pages in WebForms 4.0

Async pages in WebForms version 4.0 are based around APM. As such, you will break part of the processing into a method that starts the async I/O and returns an IAsyncResult, and another part that gets the results of the async I/O and uses the results. The asynchronous page lifecycle is shown in Figure 9-8.

9781430259206_Fig09-08.jpg

Figure 9-8. Asynchronous page lifecycle

You need to tell ASP.NET that there are asynchronous parts to the page execution, and you will do this in two parts: mark the page as asynchronous in the .ASPX file, and register the asynchronous methods early in the page lifecycle (Page_Load is a convenient hook). Let’s step through the implementation.

Mark the Page As Asynchronous

Marking the page as asynchronous is very straightforward. Simply add the attribute Async="true" to the @page directive in the .ASPX file, as shown in Listing 9-5.

Listing 9-5.  Marking a Page As Asynchronous

<%@ Page Async="true"Language="C#" AutoEventWireup="true"
         CodeBehind="AsyncPageThreading.aspx.cs" Inherits="Dotnet40.AsyncPageThreading" %>
  
<!DOCTYPE html>
  
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Label runat="server" ID="outputT1"></asp:Label>
    </div>
    <div>
        <asp:Label runat="server" ID="outputT2"></asp:Label>
    </div>
    </form>
    <div>
        <asp:Label runat="server" ID="output"></asp:Label>
    </div>
</body>
</html>

Registering the Asynchronous Methods

You can register asynchronous methods using the AddOnPreRenderCompleteAsync API. This takes two delegate parameters of type BeginEventHandler and EndEventHandler, respectively, which both follow the APM pattern. BeginEventHandler, after the standard object and EventArgs parameters of events, takes an AsyncCallback delegate and object, and it returns an IAsyncResult. The EndEventHandler takes an IAsyncResult as a single parameter and returns void.

You can put the call to AddOnPreRenderCompleteAsync in your page load handling, but you should make sure that it is only processed the first time the page is rendered. You do not want to be going to the database if the user has submitted a form or pressed a button on the page, so you should only register the async methods if you are not in a post-back (see Listing 9-6).

Listing 9-6.  Registering the Asynchronous Methods

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        AddOnPreRenderCompleteAsync(StartGetData, EndGetData);
    }
}
 
private void EndGetData(IAsyncResult ar)
{
    // details omitted for clarity
}
 
private IAsyncResult StartGetData(object sender, EventArgs e,
                                  AsyncCallback cb, object extradata)
{
    // details omitted for clarity
}

Implementing the “Begin” Method

The key responsibility of the StartGetData method in Listing 9-6 is to initiate the asynchronous I/O. It must also return an object that signals when the I/O has completed and the results can be processed; this signaling object must implement IAsyncResult. If there is only one asynchronous I/O operation to be performed, the implementation is very straightforward. The method simply hands off to the I/O operation and returns the IAsyncResult that it returns. Listing 9-7 shows an example of this.

Listing 9-7.  Implementing a Simple Initiating Async Method

AuthorRepository repo = new AuthorRepository();
 
private IAsyncResult StartGetData(object sender, EventArgs e,
                                  AsyncCallback cb, object extradata)
{
    return repo.BeginGetAuthors(cb, extradata);
}

Implementing the “End” Method

The EndGetData method from Listing 9-6 must pick up the results of the async I/O operation and process them in some way. Bear in mind with APM that the EndXXX method of the async I/O may throw an exception, and so any specific error handling you want to be performed must be placed around that call. The EndGetData method will also be responsible for any cleanup of resources committed in the StartGetData method. You can see the implementation of the EndGetData method in Listing 9-8.

Listing 9-8.  Implementing a Simple Completing Async Method

private void EndGetData(IAsyncResult ar)
{
    IEnumerable<Author> authors = repo.EndGetAuthors(ar);
    output.Text = authors.Count().ToString();
}

Dealing with Multiple Asynchronous I/O Requests

We’ve looked at the simple case of a single async I/O operation, but what if you have to perform more than one in the rendering of the page? The solution shown in Listing 9-7 will not work because the IAsyncResult returned does not represent all of the async work in progress. For this more complex case you need an implementation of IAsyncResult that represents all of the async I/O work—that means you need a custom implementation.

There are a number of ways in which you can create a general purpose IAsyncResult implementation. If the number of asynchronous operations being managed is fixed, then you can use an implementation that keeps a count internally of the outstanding operations; if you need more flexibility, then you could have an implementation that is explicitly set to complete by the consuming code. Listing 9-9 shows an implementation of an internally counting version of IAsyncResult.

Listing 9-9.  Implementation of a CountingAsyncResult

public class CountingAsyncResult : IAsyncResult
{
    private readonly object state;
    private readonly AsyncCallback callback;
  
    // For the implementation of IAsyncResult.AsyncCallback
    private readonly ManualResetEventSlim asyncEvent = new ManualResetEventSlim();
  
    private int outstandingAsyncOperations;
  
    // Store the callback and state passed
    public CountingAsyncResult(AsyncCallback callback, object state, int asyncOperationCount)
    {
        this.state = state;
        this.callback = callback ?? delegate { };
        outstandingAsyncOperations = asyncOperationCount;
    }
  
    // Called by consumer to state an async operation has completed
    public void OperationComplete()
    {
        if (outstandingAsyncOperations == 0)
        {
          throw new InvalidOperationException("All expected operations have already completed");
        }
  
        // signal the event object
        int currentOutstanding = Interlocked.Decrement(ref outstandingAsyncOperations);
        if (currentOutstanding == 0)
        {
            asyncEvent.Set();
            callback(this);
        }
    }
  
    public object AsyncState
    {
        get { return state; }
    }
  
    public WaitHandle AsyncWaitHandle
    {
        get { return asyncEvent.WaitHandle;}
    }
  
    public bool CompletedSynchronously
    {
        get { return false; }
    }
  
    public bool IsCompleted
    {
        get { return outstandingAsyncOperations == 0;}
    }
  
    // Clean up the event as it may have allocated a WaitHandle
    public void Dispose()
    {
        asyncEvent.Dispose();
    }
}

WHY NOT JUST WRAP THE I/O IN A DELEGATE?

Creating custom implementations of IAsyncResult seems like a lot of work when you could simply create a delegate, execute the I/O synchronously inside the delegate, and then invoke the delegate asynchronously. This would give you an IAsyncResult that modeled all of the I/O work. So why not take this approach? After all, it still runs asynchronously to the page.

The critical issue is where asynchronous delegate runs. Asynchronous delegate invocation runs on worker threads in the thread pool, which means the thread is blocked while the I/O takes place (something you’re trying to avoid), but more importantly you are handing the async I/O work to the same group of threads that are trying to process ASP.NET requests, which means you might as well have left the processing on the request thread in the first place.

Now you can use a counting IAsyncResult version in a more complex asynchronous page that performs two async I/O operations during its rendering. Listing 9-10 shows the implementation of the page; notice that the CountingAsyncResult is initialized in the StartGetData method. As the async I/O operations complete (inside the AsyncCallback passed to the Begin operations on the repositories), asyncResult is signaled to say that one of the operations has completed.

Listing 9-10.  Complex Async Page Using a CountingAsyncResult

public partial class ComplexAsyncPage : System.Web.UI.Page
{
    private AuthorRepository authorRepo = new AuthorRepository();
    private TitleRepository titleRepo = new TitleRepository();
  
    private IAsyncResult authorIar;
    private IAsyncResult titleIar;
  
    private CountingAsyncResult asyncResult;
  
    protected void Page_Load(object sender, EventArgs e)
    {
        AddOnPreRenderCompleteAsync(StartGetData, EndGetData);
    }
  
    private void EndGetData(IAsyncResult ar)
    {
        try
        {
            int authorCount = authorRepo.EndGetAuthors(authorIar).Count();
            int titleCount = titleRepo.EndGetTitles(titleIar).Count();
  
            output.Text = (authorCount + titleCount).ToString();
        }
        finally
        {
            asyncResult.Dispose();
        }
    }
  
    private IAsyncResult StartGetData(object sender, EventArgs e,
                                      AsyncCallback cb, object extradata)
    {
        asyncResult = new CountingAsyncResult(cb, extradata, 2);
  
        authorIar = authorRepo.BeginGetAuthors(iar =>
                        {
                            asyncResult.OperationComplete();
                        }, null);
  
        titleIar = titleRepo.BeginGetTitles(iar =>
                        {
                            asyncResult.OperationComplete();
                        }, null);
  
        return asyncResult;
    }
}

image Caution  In the “End” method HttpContext.Current will be null. If you are using APIs or components that rely on this value being set (as it is in synchronous page processing throughout the page life cycle), then you will either have to change the code so it is not reliant on HttpContext.Current (which will also make the code easier to unit test) or put the following line of code before any of these API calls in your “End” method:

HttpContext.Current = Context;

Handling Errors in Asynchronous Pages Using APM

There is a nasty gotcha lurking in the code in Listing 9-10: what happens if an exception is thrown during processing? For most of the code the normal ASP.NET error handling will be invoked and the browser will see an error page. However, if the exception is thrown during the AsyncCallback of one of the async I/O methods, then the page processing will hang (as there is no signal that that operation has completed) and the browser will get a timeout. This is because ASP.NET is completely unaware of the thread on which the exception occurs. The problem is, how can you use exception handling to make sure an error ends up on the request thread?

ASP.NET has its own SynchronizationContext, which allows you to marshal processing on to the right request thread. If you can push the exception on to the request thread, then ASP.NET will handle it normally. You could use SynchronizationContext.Post to run all of the AsyncCallback code on the request thread. This works, although you have to be very careful to ensure that, whether or not an exception occurs, the operation gets marked as completed (see Listing 9-11).

Listing 9-11.  Error Handling Using SynchronizationContext.Post

SynchronizationContext ctx = SynchronizationContext.Current;
  
authorIar = authorRepo.BeginGetAuthors(iar =>
                 {
                      ctx.Post(_ =>
                          {
                              try
                              {
                                  OperationThatCouldThrowException();
                              }
                              finally
                              {
                                  asyncResult.OperationComplete();                              }
                          }, null);
                 }, null);

What if you need to make sure the standard code execution stays on the background thread? In that case you need to do something more subtle. You will have to put a try/catch block around the code that could throw the exception and then do something more inventive in the catch block. The problem is you don’t want to lose the stack trace from the original exception, so you have to package the original exception as an inner exception to a wrapper and throw the wrapper using SynchronizationContext.Send (you don’t want the operation marked as complete before you have pushed the error to the request thread). You can see an example in Listing 9-12.

Listing 9-12.  Fine-Grained Error Handling with SynchronizationContext.Send

SynchronizationContext ctx = SynchronizationContext.Current;
  
authorIar = authorRepo.BeginGetAuthors(iar =>
                 {
                     try
                     {
                         OperationThatCouldThrowException();
                     }
                     catch (Exception x)
                     {
                         ctx.Send(_ =>
                              {
                                  throw new
                                    WrapperException("An error occurred during processing", x);
                              }, null);
                     }
                     finally
                     {
                         asyncResult.OperationComplete();
                     }
                 }, null);

WebForms 4.0, then, has asynchronous functionality built in, although it does require some effort to do anything beyond the simplest case. Fortunately WebForms 4.5 addresses this issue.

Asynchronous Pages in WebForms 4.5

The introduction of TPL in .NET 4.0 and the resulting BCL changes in .NET 4.5—along with async and await in C#5—have hugely simplified creating async pages in WebForms. However, now you will need a TPL-friendly version of GetAuthors that returns a Task<IEnumerable272103_1_En> rather than IEnumerable272103_1_En (Listing 9-13).

Listing 9-13.  TPL-Friendly Version of GetAuthors

private const string connStr = "Server=.;Database=pubs;Integrated Security=SSPI";
 
public async Task<IEnumerable<Author>>GetAuthorsAsync()
{
    var authors = new List<Author>();
    using (var conn = new SqlConnection(connStr))
    {
        using (var cmd = new SqlCommand("GetAuthors", conn))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            conn.Open();
  
            using (SqlDataReader reader = await cmd.ExecuteReaderAsync())
            {
                while (reader.Read())
                {
                    authors.Add(new Author
                        {
                            FirstName = (string)reader["au_fname"],
                            LastName = (string)reader["au_lname"]
                        });
                }
            }
        }
    }
  
    return authors;
}

image Note  From .NET 4.5 onward, you no longer need to specify the Asynchronous Processing = true flag on a SqlConnection connection string to be able to execute asynchronous queries.

To create an asynchronous page, instead of implementing APM, all you now need to do is to model the code that performs the async I/O operation as a method or lambda that returns a Task. You now tell ASP.NET about the Task using the RegisterAsyncTask method on the Page class. With async and await this becomes very easy, as Listing 9-14 demonstrates.

Listing 9-14.  Simple Asynchronous Page Using async and await

public partial class AsyncPage : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        RegisterAsyncTask(new PageAsyncTask(GetDataAsync));
    }
  
    async Task GetDataAsync()
    {
        var repo = new AuthorRepository();
  
        IEnumerable<Author> authors = await repo.GetAuthorsAsync();
  
        output.Text = authors.Count().ToString();
    }
}

WHY NOT MAKE PAGE_LOAD ASYNC?

In this simple case you could have simply marked Page_Load as async and put the asynchronous I/O code directly in there. This would have worked, but is generally discouraged by the ASP.NET team. They have had to jump through a lot of hoops to try to ensure that the request does not finish before the async work completes. This is complex because Page_Load does not return a Task so the ASP.NET team has to infer what is happening in an async event handler.

For anything complex you should use RegisterAsyncTask. As code has a tendency to evolve over time, it is safer always to use this method rather than rely on making Page_Load asynchronous.

In fact, even with more complex pages that perform multiple async operations, the code is straightforward as you can use Task.WhenAll to generate a Task that models the completion of all of the asynchronous I/O. You can see how simple the code is in Listing 9-15, compared to the equivalent code in 4.0, shown in Listing 9-10.

Listing 9-15.  Complex Async Page Using Task.WhenAll

public partial class ComplexAsyncPage : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        RegisterAsyncTask(new PageAsyncTask(GetDataAsync));
    }
  
    async Task GetDataAsync()
    {
        var authorRepo = new AuthorRepository();
        var titleRepo = new TitleRepository();
  
        Task<IEnumerable<Author>> authorsTask = authorRepo.GetAuthorsAsync();
        Task<IEnumerable<Title>> titlesTask = titleRepo.GetTitlesAsync();
                    
        await Task.WhenAll(authorsTask, titlesTask);
  
        int authorCount = authorsTask.Result.Count();
        int titleCount = titlesTask.Result.Count();
  
        output.Text = (authorCount + titleCount).ToString();
    }
}

image Note  Unlike WebForms 4.0, in the 4.5 Task-based pattern HttpContext.Current will be set correctly for all code in the page life cycle.

You do not need to do anything special with error handling. Just use try/catch blocks as you normally would. The awaiter makes sure that any errors end up on the correct thread.

With WebForms you have seen that, whether using 4.0 or 4.5, you can make your page processing more efficient and scalable in the face of async I/O. However, in 4.5 life gets much simpler and the required code is far closer to the synchronous version than in 4.0.

ASP.NET MVC

MVC takes a very different approach to web programming than WebForms. Rather than trying to provide a similar model to smart client control-based UI, it embraces the Web and HTTP to provide an API that gives you direct control over HTML, CSS, and JavaScript. To understand the separation of concerns, let’s look at a simple synchronous example, and then how you can turn a synchronous MVC application into an asynchronous one.

Remember that the original GetAuthors method is synchronous and long running. Listing 9-16 shows this synchronous operation being used from inside a controller, the HomeController. The MVC routing infrastructure maps requests to the appropriate controller method, known as an action based on pattern matching of the URI and HTTP verb. In this case the Index action will be invoked. The Index action gets the authors from the repository and selects the Index view, passing the authors as the data that the view will display.

Listing 9-16.  Synchronous Controller Using Synchronous Repository Operation

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var repo = new AuthorRepository();
  
        IEnumerable<Author> authors = repo.GetAuthors();
  
        return View("Index", authors);
    }
}

WHAT DOES MVC MEAN?

MVC stands for Model, View, Controller. These are the three concepts into which code is separated in an MVC application, and each has a distinct responsibility. One of the core goals of MVC is to make as much of the application code easily unit testable as possible, and as such we try to keep as much of the code as simple classes rather than using ASP.NET functionality directly.

The Model is the business logic. This is how data is accessed and business rules applied.

The View is the rendering of the UI. The only logic in here is what is necessary for rendering the view correctly

The Controller’s job is orchestration. Its role is to interact with the Model, select the View, and present the data from the Model to the View. It does not contain business logic, nor does it determine the appearance of the UI.

Both the Model and the Controller should be unit testable as normal classes. It is only the View that will require manual or specialized UI/HTML-based test frameworks.

Listing 9-17 shows the view code, using a markup language called Razor that allows you to embed C# inside the HTML to inject the view logic. You can see that each author is displayed as a list item in an unordered list. The interaction of the routing infrastructure, controller action, repository, and view turns the HTTP request into an HTML page to display.

Listing 9-17.  Index View to Display the Authors

@{
    ViewBag.Title = "Index";
}
  
<h2>Authors</h2>
<ul>
    @foreach (Dotnet40MVC.Models.Author author in Model)
    {
        <li>
            <span>@author.FirstName</span>
            <span>@author.LastName</span>
        </li>
    }
</ul>

Again, the underlying GetAuthors stored procedure is very slow, so now let’s look at what you need to do to make this processing asynchronous with respect to the request thread. It turns out that two things are needed: the repository needs to use asynchronous I/O, and the controller needs to be able to release the request thread during the async I/O.

Asynchronous MVC Processing in .NET 4.0

MVC is generally released to the Web, although a version is packaged with the .NET framework each time it ships. A version commonly used with .NET 4.0 is MVC 3, and so we will look at how to build asynchronous functionality with that version of MVC. MVC 4 does work with .NET 4.0 and does take advantage of TPL, but the real benefit doesn’t really appear until you have the I/O classes that use TPL and the async and await keywords in .NET 4.5. We will look at MVC 4 shortly, but for the time being we will stay with MVC 3. Like most of .NET 4.0, MVC 3 does not take advantage of TPL, and so let’s model our asynchronous repository functionality using EAP (Listing 9-18).

Listing 9-18.  Asynchronous GetAuthors Using EAP

public void GetAuthorsEAP()
{
    var syncCtx = SynchronizationContext.Current;
  
    var conn = new SqlConnection(connStr);
    var cmd = new SqlCommand("GetAuthors", conn);
    cmd.CommandType = CommandType.StoredProcedure;
    conn.Open();
  
    cmd.BeginExecuteReader(iar =>
        {
            try
            {
                using (SqlDataReader reader = cmd.EndExecuteReader(iar))
                {
                    var authors = new List<Author>();
                    while (reader.Read())
                    {
                        authors.Add(new Author
                            {
                                FirstName = (string) reader["au_fname"],
                                LastName = (string) reader["au_lname"]
                            });
                    }
  
                    var args = new GetAuthorsCompletedEventArgs(authors);
  
                    syncCtx.Post(_ =>
                    {
                        GetAuthorsCompleted(this, args);
                    }, null);
                }
            }
            finally
            {
                cmd.Dispose();
                conn.Dispose();
            }
        }, null);
}
    
public event EventHandler<GetAuthorsCompletedEventArgs> GetAuthorsCompleted = delegate { };

Asynchrony is implemented within MVC at the controller level. To be able to make actions asynchronous, you need to derive your controller not from Controller directly but rather from the AsyncController class. AsyncControlleritself derives from Controller, so you can still have synchronous actions on the controller as well as asynchronous ones—AsyncController simply makes it possible to have asynchronous actions. To make an action asynchronous, you must split its functionality into two methods: one that starts the processing and one that returns the ActionResult. These two methods have specific names: <action name>Async and <action name>Completed, so for our Index action they would be IndexAsync and IndexCompleted.

Implementing IndexAsync

IndexAsync has three responsibilities:

  1. Set up the constructs that will tell MVC that the async I/O has completed
  2. Execute the asynchronous I/O-based operations
  3. Put the results of the asynchronous I/O somewhere that allows them to be passed to the IndexCompleted method

IndexAsync can take any route- and request-based parameters, such as IDs. However, in this case there are no specific inputs into the action, so it will take no parameters. It interacts with the asynchronous controller via the AsyncManager property on AsyncController. AsyncManager allows you to specify how many asynchronous operations are going to be executed within the action; to say when an asynchronous operation has completed and to store the results of that operation (see Listing 9-19). The name given as the index into the AsyncManager’s Parameters dictionary is important as this will be mapped to a corresponding parameter name in IndexCompleted. Note that both Increment and Decrement on the OutstandingOperations count can take integers if the count needs to be adjusted by more than 1. When the OutstandingOperations count goes to zero, then the IndexCompleted method will be called.

Listing 9-19.  The Implementation of IndexAsync

public void IndexAsync()
{
    AsyncManager.OutstandingOperations.Increment();
  
    var repo = new AuthorRepository();
  
    repo.GetAuthorsCompleted += (s, e) =>
        {
            AsyncManager.Parameters["authors"] = e.Authors;
            AsyncManager.OutstandingOperations.Decrement();
        };
        
    repo.GetAuthorsEAP();
}

image Caution  If the calls to Increment and Decrement on the OutstandingOperations count become unbalanced, then the IndexCompleted method will never execute—even if the count goes negative. The IndexCompleted method is called only when the count goes to exactly zero.

Implementing the IndexCompleted Method

The IndexCompleted method looks very much like a normal action, as it typically returns an ActionResult. The parameters that get passed to it are the named items from the AsyncManager.Parameters dictionary, so in this case it will take an IEnumerable272103_1_En parameter called authors (Listing 9-20). Matching the names is important, as otherwise the parameter will be the default for that type (null, 0, false, etc.).

Listing 9-20.  The Implementation of IndexCompleted

public ActionResult IndexCompleted( IEnumerable<Author> authors)
{
    return View("Index", authors);
}

Take Care with APM

The code in IndexAsync is straightforward, partly because EAP uses an available SynchronizationContext to marshal the completed event onto a known thread. In the case of ASP.NET this thread is a thread with the right HttpContext. With APM you get no such guarantee, so in an AsyncCallback you cannot use the controller instance safely and HttpContext.Current is null. This also means you cannot safely add results to AsyncManager.Parameters, as this is simply a Dictionary<string,object> and is not thread safe—if two AsyncCallbacks were to run at the same time, they could corrupt the dictionary.

To solve this problem, you could use the SynchronizationContext yourself and do a Post or Send (remember—if using Post you must Decrement the OutstandingOperations count in your SendOrPostCallback, as it runs asynchronously and would otherwise cause a race condition). Alternatively, you can use the AsyncManager Sync method, which takes an Action that it will execute using SynchronizationContext.Send.

Performing Multiple Async I/O Operations Within an Asynchronous Action

You saw that with WebForms 4.0, performing multiple async I/O operations within a page was relatively complex. Fortunately, with MVC 3 the world is a lot simpler—especially if your async I/O operations are modeled with EAP. You can see an implementation in Listing 9-21 that simply states the number of async operations and then, as the results come in, stores the results and decrements the count of the OutstandingOperations property of AsyncManager.

Listing 9-21.  Asynchronous Action with Two Async I/O Operations

public void FullAsync()
{
    AsyncManager.OutstandingOperations.Increment(2);
  
    var authorRepo = new AuthorRepository();
    authorRepo.GetAuthorsCompleted += (s, e) =>
        {
            AsyncManager.Parameters["authors"] = e.Authors;
            AsyncManager.OutstandingOperations.Decrement();
        };
    authorRepo.GetAuthorsEAP();
  
    var titleRepo = new TitleRepository();
    titleRepo.GetTitlesCompleted += (s, e) =>
        {
            AsyncManager.Parameters["titles"] = e.Titles;
            AsyncManager.OutstandingOperations.Decrement();
        };
    titleRepo.GetTitlesEAP();
}
  
public ActionResult FullCompleted(IEnumerable<Author> authors, IEnumerable<Title> titles )
{
    return View("Full", new FullViewModel{Authors = authors, Titles = titles});
}

MVC 3 has reasonably succinct infrastructure for asynchronous processing. However, one of our goals is to try to make the asynchronous code as easy to understand as the synchronous code. In MVC 3’s async pattern, the asynchronous code looks very different from the synchronous code with the action being separated into two methods. MVC 4 has TPL at its disposal so hopefully it can do even better.

Asynchronous MVC Processing in .NET 4.5

MVC 4 ships with .NET 4.5 and leverages the availability of TPL. In fact, the asynchronous code in MVC 4 is almost identical to the synchronous code—the action method just returns Task<ActionResult>, is marked as async, and uses await on the async I/O operation.

You can see the similarity between the synchronous and asynchronous code when you compare the MVC 4 asynchronous code in Listing 9-22 and the synchronous code in Listing 9-16. Note that there is no need to derive from AsyncController in MVC 4.

Listing 9-22.  An Asynchronous Action in MVC 4

public class HomeController : Controller
{
    public async Task<ActionResult>Index()
    {
        var repo = new AuthorRepository();
  
        IEnumerable<Author> authors = await repo.GetAuthorsAsync();
        return View("Index", authors);
    }
}

It’s very straightforward to deal with one async I/O operation; what about more than one? You’ve seen that this can bring extra complexity, so how does this work in MVC 4? Again, Task.WhenAll comes to your rescue as it allows you to take a set of async operations and wait for all of them to finish in an async/await-friendly way (Listing 9-23).

Listing 9-23.  An Action with Multiple Async I/O Operations in MVC 4

public async Task<ActionResult> Full()
{
    var authorRepo = new AuthorRepository();
    var titleRepo = new TitleRepository();
  
    var authorsTask = authorRepo.GetAuthorsAsync();
    var titlesTask = titleRepo.GetTitlesAsync();
  
    await Task.WhenAll(authorsTask, titlesTask);
  
    IEnumerable<Author> authors = authorsTask.Result;
    IEnumerable<Title> titles = titlesTask.Result;
  
    return View("Full", new FullViewModel {Authors = authors, Titles = titles});
}

Once more you see that although .NET 4.0 supports asynchronous invocation via IOCP, the programming model is much simpler under .NET 4.5.

ASP.NET Web API

We have now looked at the two UI web technologies in the .NET framework. There is, however, a new API on the block: one for building server-side code to be consumed by other code. This is ASP.NET Web API, a framework for building HTTP-based services that use the REST model for exposing functionality. Web API uses HTTP verbs and URIs to define the functionality that will be invoked, and as such fits in very well with the ASP.NET MVC view of the world. As a result, Web API leverages part of the MVC infrastructure to expose its functionality. Web API ships as part of MVC 4 and so is available on both .NET 4.0 and 4.5. We will look at the asynchronous programming model in both; but first, as previously, we will start with a synchronous version.

WHAT IS REST?

REST, or REpresentational State Transfer, is an architectural style for building web-accessible services. Roy Fielding proposed this model in his doctoral thesis in 2000, and it has gained a lot of traction in recent times, particularly for public-facing APIs.

REST has a very different approach to SOAP-based models. SOAP uses self-describing messages that can be sent over arbitrary transports. REST embraces HTTP as an application protocol rather than purely as a transport, thus mirroring the model of the World Wide Web. In REST, functionality is partitioned into resources that have a Uniform Resource Identifier (URI). You then state how you want to manipulate that resource via the HTTP verb you use—for example, a GET is a read of the resource, whereas a DELETE deletes the resource.

Like all architectural models, there is a lot of debate about implementation details. However, REST has become one of the key architectures for building scalable web-facing services.

Web API uses a controller to model the HTTP verbs for the URI that maps to that controller. Mapping to the controller uses the same kind of pattern matching that ASP.NET MVC uses. So for a simple controller that gets the authors, use the code in Listing 9-24.

Listing 9-24.  Synchronous Web API Request

public class AuthorsController : ApiController
{
    // GET api/authors
    public IEnumerable<Author> Get()
    {
        var repo = new AuthorRepository();
  
        return repo.GetAuthors();
    }
}

Asynchronous Web API Operations in .NET 4.0

As you are using MVC 4, you have the same simplified Task-based asynchronous model. However, the APIs you will typically have to deal with will be APM or EAP based. To combine APM and Tasks you need to use an adapter to change the APM code into a Task. Fortunately, as you saw in Chapter 3, you have the FromAsync method of the TaskFactory for just this purpose. You can see the resulting async version of the Web API method in Listing 9-25.

Listing 9-25.  Asynchronous Web API Operation in .NET 4.0

public class AuthorsController : ApiController
{
    // GET api/authors
    public Task<IEnumerable<Author>>Get()
    {
        var repo = new AuthorRepository();
  
        return Task.Factory.FromAsync<IEnumerable<Author>>(repo.BeginGetAuthors,
                                                           repo.EndGetAuthors,
                                                           null);
    }
}

In the simple case, then, Web API with .NET 4.0 is very straightforward but, as always, composing multiple operations brings more complexity. .NET 4.0 doesn’t have Task.WhenAll, which is the ideal way to combine two tasks, but you can achieve the same end, with a little more work, with TaskCompletionSource. You can see the implementation in Listing 9-26.

Listing 9-26.  Composing Multiple Operations with Web API and TaskCompletionSource

public Task<FullResponse>Get()
{
    var authorRepo = new AuthorRepository();
    var titleRepo = new TitleRepository();
  
    var tcs = new TaskCompletionSource<FullResponse>();
  
    var response = new FullResponse();
  
    int outstandingOperations = 2;
  
    Task.Factory.FromAsync(authorRepo.BeginGetAuthors(null, null), iar =>
        {
            response.Authors = authorRepo.EndGetAuthors(iar);
            int currentCount = Interlocked.Decrement(ref outstandingOperations);
            if (currentCount == 0)
            {
                tcs.SetResult(response);
            }
        });
  
    Task.Factory.FromAsync(titleRepo.BeginGetTitles(null, null), iar =>
        {
            response.Titles = titleRepo.EndGetTitles(iar);
            int currentCount = Interlocked.Decrement(ref outstandingOperations);
            if (currentCount == 0)
            {
                tcs.SetResult(response);
            }
        });
  
    return tcs.Task;
}

You can see that, as a result of MVC 4 having a Task-based approach to asynchrony, creating asynchronous Web API operations, even in .NET 4.0, is not too complex. But as you have seen previously, with Task becoming ubiquitous in the framework API and asynchrony being supported in languages, life gets even easier.

Asynchronous Web API Operations in .NET 4.5

You have already seen a Task-friendly version of the AuthorsRepository (Listing 9-13), so plugging this into Web API is very straightforward; you can see the result in Listing 9-27.

Listing 9-27.  Asynchronous Web API Operation in .NET 4.5

public class AuthorsController : ApiController
{
    // GET api/authors
    public Task<IEnumerable<Author>> Get()
    {
        var repo = new AuthorRepository();
  
        return repo.GetAuthorsAsync();
    }
}

You have also seen that even when you have multiple async I/O operations, with Task.WhenAll that scenario is also very simple. If you compare Listing 9-26 and Listing 9-28, you can see this simplicity in action.

Listing 9-28.  Composing Multiple Operations with Web API and Task.WhenAll

public class FullController : ApiController
{
    // GET api/Full/
    public async Task<FullResponse> Get()
    {
        var authorRepo = new AuthorRepository();
        var titleRepo = new TitleRepository();
  
        var authorTask = authorRepo.GetAuthorsAsync();
        var titleTask = titleRepo.GetTitlesAsync();
  
        await Task.WhenAll(authorTask, titleTask);
  
        var response = new FullResponse
            {
                Authors = authorTask.Result,
                Titles = titleTask.Result
            };
  
        return response;
    }
}

You have looked at three different ASP.NET technologies and how, whether using .NET 4.0 or 4.5, you can take advantage of IOCP to create efficient and scalable server-side code. You have also seen that the integration of TPL into the BCL and async and await into C# simplifies implementation—even in quite complex scenarios. However, ASP.NET is not the only server-side infrastructure in .NET; we will now move on to look at Windows Communication Foundation.

Windows Communication Foundation

Windows Communication Foundation (WCF) provides a framework for building service-based code, principally using SOAP as the basis of an architectural style. Service functionality is exposed via endpoints, which are composed of three parts:

  1. Address—where the service is listening
  2. Binding—how communication takes place on the wire: transport protocol, wire format, security, et cetera
  3. Contract—what operations are exposed, what data they need, and what they might return

WCF contracts are .NET types (commonly interfaces) annotated with the [ServiceContract] attribute. Operations are methods on the type that are annotated with the [OperationContract] attribute. .NET types can be used in the operation definition and are serialized to an XML Infoset when messages are sent over the wire. The [DataContract] and [DataMember] attributes are used to control that serialization.

Asynchrony in WCF is controlled by the contract and the service implementation, so let’s look at the synchronous model as a starting point. Listing 9-29 shows the contract and implementation of the synchronous version of a service. We will now examine how the code changes to make this service asynchronous in both .NET 4.0 and 4.5.

Listing 9-29.  Contract and Implementation of Synchronous Service

[DataContract(Name="Author", Namespace = "")]
public class AuthorDTO
{
    [DataMember]
    public string FirstName { get; set; }
    [DataMember]
    public string LastName { get; set; }
}
  
[ServiceContract]
interface IGetPubs
{
    [OperationContract]
    List<AuthorDTO> GetAuthors();
}
    
public class Service : IGetPubs
{
    AuthorRepository authorRepo = new AuthorRepository();
    TitleRepository titleRepo = new TitleRepository();
  
    public List<AuthorDTO> GetAuthors()
    {
        return authorRepo.GetAuthors()
                         .Select(a => new AuthorDTO
                             {
                                 FirstName = a.FirstName,
                                 LastName = a.LastName
                             })
                         .ToList();
    }
}

Asynchronous WCF Services in .NET 4.0

From the release of WCF in .NET 3.0 to .NET 4.0, the asynchronous model for service operations was based on APM. Rather than the interface specifying the contract having a single method, it has a pair of methods that, together, define the operation. To make sure WCF understands they are a pair of methods for one operation, the begin method has the [OperationContract] attribute with the AsyncPattern property set to true.

image Caution  The naming of the async pattern methods is very important. The methods must be called Begin<Operation Name> and End<Operation Name>. If the methods do not follow this model, the service will compile but will fail to start.

image Note  It is important to differentiate here between the interface and the contract. The interface is a means to model the contract; it is not the contract itself. So while the interface has two methods, the contract only has a single ­operation that matches the synchronous version of the method. The asynchrony is not a function of the contract but rather an implementation detail.

A Simple Asynchronous Server Operation

As you have seen before, the simple case of a single async I/O operation is fairly straightforward. You can see an example of an async contract definition in Listing 9-30.

Listing 9-30.  Asynchronous Version of the Contract Using APM

[ServiceContract]
interface IGetPubs
{
    [OperationContract( AsyncPattern = true)]
    IAsyncResult BeginGetAuthors(AsyncCallback callback, object state);
    List<AuthorDTO> EndGetAuthors(IAsyncResult iar);
}

As usual with APM, the Begin method must return a call object that signals when the async I/O work is complete, and the End method retrieves the results. Because the AuthorRepository supports APM, the implementation of these methods is very straightforward—simply return the IAsyncResult you get from the repository as in Listing 9-31.

Listing 9-31.  Implmentation of the Async Contract Using APM

public IAsyncResult BeginGetAuthors(AsyncCallback callback, object state)
{
    return authorRepo.BeginGetAuthors(callback, state);
}
  
public List<AuthorDTO> EndGetAuthors( IAsyncResult iar)
{
    return authorRepo.EndGetAuthors(iar)
                     .Select(a => new AuthorDTO
                         {
                             FirstName = a.FirstName,
                             LastName = a.LastName
                         })
                     .ToList();
}

Complex Asynchronous Service Operations

You have already seen that the real complexity with service-side APM is when you need to perform multiple async I/O operations within a service request. You have also seen that you can achieve a fairly concise implementation if you use a custom IAsyncResult implementation. However, as you are using .NET 4.0, there is another approach you can take—one that takes advantage of the fact that Task implements IAsyncResult. You can use TaskCompletionSource as an adapter to turn multiple APM operations into a Task and, therefore, an implementation of IAsyncResult (see Listing 9-32). Notice that for this to work you need to pass the state parameter to the TaskCompletionSource constructor so it ends up as the AsyncState of the IAsyncResult implementation.

Listing 9-32.  Complex Service Operation Using TaskCompletionSource

FullDetails response = new FullDetails();
  
public IAsyncResult BeginGetAuthorsAndTitles(AsyncCallback callback, object state)
{
    var tcs = new TaskCompletionSource<FullDetails>(state);
    int outstandingOperations = 2;
  
    authorRepo.BeginGetAuthors(iar =>
        {
            response.Authors = authorRepo.EndGetAuthors(iar)
                                         .Select(a => new AuthorDTO
                                               {
                                                   FirstName = a.FirstName,
                                                   LastName = a.LastName
                                               })
                                         .ToList();
            int currentOutstanding = Interlocked.Decrement(ref outstandingOperations);
            if (currentOutstanding == 0)
            {
                tcs.SetResult(response);
                callback(tcs.Task);
            }
        }, null);
  
    titleRepo.BeginGetTitles(iar =>
        {
            response.Titles = titleRepo.EndGetTitles(iar)
                                       .Select(a => new TitleDTO()
                                             {
                                                 Name = a.Name,
                                                 Price = a.Price == 0.0m ?
                                                                 (decimal?)null : a.Price
                                             })
                                       .ToList();
            int currentOutstanding = Interlocked.Decrement(ref outstandingOperations);
            if (currentOutstanding == 0)
            {
                tcs.SetResult(response);
                callback(tcs.Task);
            }
        }, null);
  
    return tcs.Task;
}
  
public FullDetails EndGetAuthorsAndTitles(IAsyncResult iar)
{
    return response;
}

image Note  In Listing 9-32 we are assuming an InstanceContextMode of PerCall or at least a ConcurrencyMode of Single. If multiple requests enter one instance, then there will be contention of the response member variable, and one of the requests will likely receive the wrong response.

However, as you saw with WebForms, things can start to get interesting when you try to weave error handling into the implementation.

Error Handling in APM-Based Services

In WCF, errors are expressed by throwing a special exception type called a FaultException. This gets translated by the WCF infrastructure into a SOAP fault message, which is a platform-independent way of stating something has gone wrong in processing. This is the familiar problem of how to throw this FaultException on the right thread, so WCF is aware of the issue.

Unfortunately, in WCF you do not have access to an implementation of SynchronizationContext, so youcannot use the same techniques that you saw in WebForms. However, one advantage you do have is, by design,fault messages should not contain things like stack traces, as they can expose security-sensitive information to remote callers. So as long as you can capture the fact that a FaultException needs to be thrown, you can simply store the fault message information you need to send (see Listing 9-33).

Listing 9-33.  Error Handling in APM-Based Service

FullDetails response = new FullDetails();
private string faultMessage = null;
  
public IAsyncResult BeginGetAuthorsAndTitles(AsyncCallback callback, object state)
{
    var tcs = new TaskCompletionSource<FullDetails>(state);
    int outstandingOperations = 2;
  
    authorRepo.BeginGetAuthors(iar =>
           {
               try
               {
                   response.Authors = authorRepo.EndGetAuthors(iar)
                                                .Select(a => new AuthorDTO
                                                    {
                                                        FirstName = a.FirstName,                                                        LastName = a.LastName
                                                     })
                                                .ToList();
                }
                catch (Exception x)
                {
                    faultMessage = "Error retrieving authors";
                }
                finally
                {
                    int currentOutstanding = Interlocked.Decrement(ref outstandingOperations);
                    if (currentOutstanding == 0)
                    {
                        tcs.SetResult(response);
                        callback(tcs.Task);
                    }
                }
            }, null);
 
            // rest of method omitted for clarity
  
            return tcs.Task;
        }
  
        public FullDetails EndGetAuthorsAndTitles(IAsyncResult iar)
        {
            if (faultMessage != null)
            {
                throw new FaultException(faultMessage);
            }
  
            return response;
        }

This, then, is the asynchronous model on the service side of WCF prior to .NET 4.5. It involves modifying the contract interface to use APM and then providing an implementation of that interface that hands off to the required async I/O methods. This ensures that the request thread is free to process further requests while the I/O takes place. However, you can see that there are a number of places where the code becomes complex to maintain, as the service performs more asynchronous work.

Asynchronous WCF Services in .NET 4.5

As with other server-side frameworks, asynchronous WCF has had an overhaul in .NET 4.5 to take advantage of TPL. Instead of modeling an asynchronous implementation of a contract operation using APM, now you can simply use Task as a return type, as you can see in Listing 9-34.

Listing 9-34.  TPL-Based WCF Contract

[ServiceContract]
interface IGetPubs
{
    [OperationContract]
    Task<List<AuthorDTO>>GetAuthors();
  
    [OperationContract]
    Task<FullDetails>GetAuthorsAndTitles();
}

You have seen, in the previous examples of TPL-based server-side APIs, that using Tasks also removes a lot of the complexity in the implementation code, as shown by the GetAuthors implementation in Listing 9-35.

Listing 9-35.  TPL-Based Implementation of GetAuthors Operation

public async Task<List<AuthorDTO>> GetAuthors()
{
    IEnumerable<Author> authors = await authorRepo.GetAuthorsAsync();
  
    return authors.Select(a => new AuthorDTO
           {
               FirstName = a.FirstName,
               LastName = a.LastName
           }).ToList();
}

Last, as you have seen, even complex asynchronous operations are heavily simplified by the use of Task.WhenAll to combine the wait for multiple async I/O operations into a single Task. Listing 9-36 shows the elegance of this approach, which leaves you with asynchronous code that has the same structure and error-handling approach as a synchronous version of the operation.

Listing 9-36.  Implementation of Complex Operation Using TPL

public async Task<FullDetails>GetAuthorsAndTitles()
{
    var authorTask = authorRepo.GetAuthorsAsync();
    var titleTask = titleRepo.GetTitlesAsync();
  
    await Task.WhenAll(authorTask, titleTask);
  
    var response = new FullDetails
    {
        Authors = authorTask.Result.Select(a => new AuthorDTO
            {
                FirstName = a.FirstName,
                LastName = a.LastName
            }).ToList(),
  
            Titles = titleTask.Result.Select(t => new TitleDTO()
            {
                Name = t.Name,
                Price = t.Price == 0.0m ? (decimal?)null : t.Price
            }).ToList(),
    };
            
    return response;
}

Summary

To really scale with the minimum hardware, server code requires the use of asynchronous I/O and IOCP to allow the freeing of the request thread while the I/O takes place. Because of this, the main server-side technologies have been given the ability to run requests asynchronously.

Prior to .NET 4.5 the server-side frameworks used a variety of techniques to achieve asynchrony. Although these techniques work, they can lead to complex solutions, particularly when error handling is laid in. For .NET 4.5 the frameworks have been standardized on a common model using TPL. With async and await these new asynchronous models provide a very clean and simple asynchronous API that manages to retain the simplicity of the synchronous model, while giving you the asynchronous functionality you require.

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

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