Thursday, 16 June 2011

Asynchronous calls to WCF service from Asp.Net

One of the functionalities I'm currently working on is document upload. After the document is uploaded to the server via web interface (Asp.Net Webforms) it is passed to a WCF web service which processes it and returns the response. For the whole time the user interface is locked and the end user waits for upload confirmation.

This typical synchronous scenario may be very frustrating for the users because they are blocked until the operation completes. The bigger the file to process the worse it gets.

We decided to change that so an asynchronous upload is used: Once the file is sent to the server the confirmation is immediately displayed to user. The confirmation states only that the upload process was successfully started and the user can continue working with the web app while the file is processed.

In such scenario you'll also need to display upload results at some stage. There are many possible options for displaying the final operation results (e.g. ajax calls combined with some popups, additional tab etc.). This part is not covered in this post.

Implementation:

Because the file is processed by a web service I wanted to use a WCF mechanism for asynchronous service calls. The mechanism is quite easy to use. When you generate the service proxy using Visual Studio select to "Generate asynchronous operations" (under "Advanced" options). This will add additional "Async" method for each operation and Completed event. All you need to do is set the handler for Completed event and call the Async method using generated client e.g.
client.UploadDocumentCompleted +=
new EventHandler<UploadDocumentCompletedEventArgs>(UploadDocumentCallback);
client.UploadDocumentAsync(fileToUpload);

In order to make this work on your Aspx page you'll need to add Async="True" to you page directive (see my other post for details).

Some useful links on how to call a WCF service asynchronously:

Problem:

So I implemented my async service call in code behind of my Aspx page and then it came out it's no good in my case. I was expecting that after the upload operation of the target WCF service is called my page will return response to the user and the UI will not be blocked anymore. It came out that although the service was called asynchronously the page still waits until the operation completes before sending response to the user.

I started to search for the reason of such behaviour and stumbled upon this article. It explains the concept of asynchronous service call from Asp.net pages. It works different than I expected: the async operation must complete before Page's PreRenderComplete event so the page waits for the service call results. It still allows you to boost performance (e.g. by releasing threads to the pool) but not in the way I needed.

Workaround - starting threads manually:

Because the async service proxy didn't solve my problem I decided to implement a workaround. When I need to call the upload operation of the service I create a new thread and call the service within that thread. Since the service is called in a new thread the Page doesn't wait for the service operation to complete.

Sample class for the upload thread:
public class UploadThread
{
private byte[] _byteArray;

public UploadThread(byte[] byteArray)
{
_byteArray = byteArray;
ThreadPool.QueueUserWorkItem(this.Run);
}

protected void Run(object obj)
{
try
{
MyServiceClient client = new MyServiceClient();
client.UploadDocument(_byteArray);
client.Close();
}
catch (Exception e)
{
// Handle exception here
}
}
}
You pass the doc content in the constructor and it starts a new thread that invokes the "Run" method. That method calls the service (synchronously in my case). To be more exact the thread that processes this task is taken from the ThreadPool (see line 8).

To start the thread simply call the following code from the code behind your aspx page:
new UploadThread(fileToUpload);
where fileUplaod is an array of bytes representing the doc content.

One thing to note here is that the new thread will not have direct access to HttpContext of the page. If you needed this in your thread simply pass it in constructor.

Further enhancements:

Thinking about further enhancements I decided to configure the service operation to be One Way. It means that after the client calls the service it doesn't wait for the service response. This will cause that the upload thread will be released to the pool earlier.
[OperationContract(IsOneWay = true)]
void UploadDocument(byte[] byteArray);

An additional performance enhancement may be using streams instead of byte arrays when passing documents to WCF service. I couldn't implement this in my case because of other limitations but you can find a nice example of this here.

No comments: