Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Asynchronous Communication in a WCF Service

4.70/5 (17 votes)
25 Oct 2010CPOL4 min read 111.9K   4.4K  
Asynchronous communication between a WCF Service and a console client.

Introduction

This article shows a step by step implementation of asynchronous communication in WCF.

Background

Asynchronous pattern is a very well known and common Design Pattern in systems that are built on top of executions.

Like many other technologies, this pattern has an implementation in Windows Communication Foundation.

The .NET Framework provides two Design Patterns for asynchronous operations:

  • Asynchronous operations that use IAsyncResult objects.
  • Asynchronous operations that use events.

This article uses the IAsyncResult implementation for asynchronous communication.

Need of async communication

For long running executions in an application, there is a probability that a current thread cannot keep executing because it may stop the user interface. For example, when a Windows application goes to a new state to execute a long running process, windows may freeze and even crash. One solution is to move this execution to another thread and let it follow there.

Asynchronous pattern comes into play to solve this issue, and Microsoft has a built-in mechanism for this pattern in its great .NET Framework. Microsoft's implementation of this pattern consists of these pieces:

  • Two methods for an asynchronous operation.
  • An object which implements the IAsyncCallback interface.
  • A callback delegate.

This way, the execution of a method splits into two steps. In the first step, you create the background process and start it, and in the second step, you listen for changes in the process and wait until it finishes.

Using the code

Here is a short description of the classes used in the sample project:

AsyncResult

C#
public class AsyncResult : IAsyncResult, IDisposable 
{ 
    AsyncCallback callback; 
    object state; 
    ManualResetEvent manualResentEvent; 

    public AsyncResult(AsyncCallback callback, object state) 
    { 
        this.callback = callback; 
        this.state = state; 
        this.manualResentEvent = new ManualResetEvent(false); 
    }

AsyncResult is derived from IAsyncResult.

AsyncResult has some properties, like AsyncCallback, a state object, and a ManualResetEvent which handles the waiting for the asynchronous operation. There is an IsCompleted property which returns a boolean value to specify that if the operation is completed asynchronously, a simple WaitOne(0, false) method call always returns the appropriate value for any asynchronous operation. The last point is about the Complete() method. This calls the Set() method of my ManualResetEvent to show that my event is signaled and any other awaiting thread can follow. Then I pass the current object to the callback if the callback is not null.

AddAsyncResult

C#
public class AddAsyncResult : AsyncResult 
{ 
    public readonly int number1 = 0; 
    public readonly int number2 = 0; 
    private int result; 
    public AddDataContract AddContract { get; set; } 
    public Exception Exception { get; set; } 

    public int Result 
    {
        get { return result; } 
        set { result = value; } 
    } 

    public AddAsyncResult(int num1, int num2, AsyncCallback callback, object state) 
           : base(callback, state) 
    { 
        this.number1 = num1; 
        this.number2 = num2; 
    } 

    public AddAsyncResult(AddDataContract input, AsyncCallback callback, object state) 
          : base(callback, state) 
    { 
        this.AddContract = input; 
    }
}

AddAsyncResult is derived from AsyncResult; it also holds the input and output datacontracts/entities. We need to develop this class as per requirements.

WCF Service implementation

IAddService

C#
[ServiceContract()] 
public interface IAddService 
{ 
    [OperationContract(AsyncPattern = true)] 
    [FaultContract(typeof(ErrorInfo))] 
    IAsyncResult BeginAddDC(AddDataContract input, 
                 AsyncCallback callback, object state); 

    //[FaultContract(typeof(ErrorInfo))] 
    AddDataContract EndAddDC(IAsyncResult ar); 
}

This service has the BeginAddDC method which is declared as an asynchronous method using AsyncPattern=true.

AddService

C#
public IAsyncResult BeginAddDC(AddDataContract input, AsyncCallback callback, object state) 
{ 
    AddAsyncResult asyncResult = null; 

    try 
    { 
        //throw new Exception("error intorduced here in BeginAddDC."); 
        asyncResult = new AddAsyncResult(input, callback, state); 

        //Queues a method for execution. The method executes 
        //when a thread pool thread becomes available. 
        ThreadPool.QueueUserWorkItem(new WaitCallback(CallbackDC), asyncResult); 
    } 
    catch (Exception ex) 
    { 
        ErrorInfo err = new ErrorInfo(ex.Message, "BeginAddDC faills"); 
        throw new FaultException<ErrorInfo>(err, "reason goes here."); 
    } 

    return asyncResult; 
} 

public AddDataContract EndAddDC(IAsyncResult ar) 
{ 
    AddDataContract result = null; 

    try 
    { 
        //throw new Exception("error intorduced here in EndAddDC."); 
        if (ar != null) 
        { 
            using (AddAsyncResult asyncResult = ar as AddAsyncResult) 
            { 
                if (asyncResult == null) 
                    throw new ArgumentNullException("IAsyncResult parameter is null."); 

                if (asyncResult.Exception != null) 
                    throw asyncResult.Exception; 

                asyncResult.AsyncWait.WaitOne(); 
                result = asyncResult.AddContract; 
            } 
        } 
    } 
    catch (Exception ex) 
    { 
        ErrorInfo err = new ErrorInfo(ex.Message, "EndAddDC faills"); 
        throw new FaultException<ErrorInfo>(err, "reason goes here."); 
    }

    return result; 
}

private void CallbackDC(object state) 
{ 
    AddAsyncResult asyncResult = null; 

    try 
    { 
        asyncResult = state as AddAsyncResult; 

        //throw new Exception("error intorduced here in CallbackDC."); 
        //throw new Exception("service fails"); 
        asyncResult.AddContract = InternalAdd(asyncResult.AddContract); 
    } 
    catch (Exception ex) 
    { 
        asyncResult.Exception = ex; 

        //ErrorInfo err = new ErrorInfo(ex.Message, "CallbackDC faills"); 
        //throw new FaultException<ErrorInfo>(err, "reason goes here."); 
    } 
    finally 
    { 
        asyncResult.Complete(); 
    } 
}

private int InternalAdd(int number1, int number2) 
{ 
    Thread.Sleep(TimeSpan.FromSeconds(20)); 
    return number1 + number2; 
}

BeginAddDC queues a method CallBackDC to execute it once a thread from the thread pool becomes available, and is returned immediately to the client. This CallBackDC method then performs the actual processing of the client request on a separate thread, and after finishing its processing, it signals a ManualResetEvent.

EndAddDC simply gets the actual result from IAsyncResult and returns it.

Client

The client simply creates a proxy of the service and calls the BeginAddDC method of the service. Along with business related parameters, it also passes a callback method (this is the point where the control will fall once the WCF Service execution finishes) and a state object.

C#
IAsyncResult res = service.BeginAddDC(input, new AsyncCallback(AddCallbackDC), service); 

Client callback method

C#
static void AddCallbackDC(IAsyncResult ar) 
{ 
    try 
    { 
        Console.ForegroundColor = ConsoleColor.Yellow; 
        Console.WriteLine("in AddCallbackDC"); 
        IAddService res = ar.AsyncState as IAddService; 

        if (res != null) 
        { 
            Console.WriteLine("Result returned from WCF service"); 
            Console.WriteLine(res.EndAddDC(ar).Result.ToString()); 
        } 
    } 
    catch (Exception ex) 
    { 
    } 
    finally 
    { 
        if (addProxy != null) 
        { 
            addProxy.CloseProxy(); 
            Console.WriteLine("Proxy closed."); 
        } 
        Console.ResetColor(); 
    }

This callback method extracts the service object from IAsyncResult.AsyncState and calls EndAddDC of the WCF Service to get the actual result on the client side.

Output

As you can see in the screenshot below, after providing two numbers, the client creates a proxy and calls its BeginAddDC method, and then immediately returns back to the client and continues its execution (starts printing numbers) until it receives a response from the WCF Service. After receiving a response, it displays it and continues with the main execution.

result.jpg

Handling FaultException

If any exception arises in the Begin/End method in the WCF Service, then it can be raised from there itself as a FaultException.

C#
catch (Exception ex) 
{ 
    ErrorInfo err = new ErrorInfo(ex.Message, "BeginAddDC faills"); 
    throw new FaultException<ErrorInfo>(err, "reason goes here."); 
}

But what if the exception comes in the WCF Service in the method which we are running on a separate thread (in this case, this method is CallBackDC)?

C#
private void CallbackDC(object state) 
{ 
    AddAsyncResult asyncResult = null; 
    try 
    { 
        asyncResult = state as AddAsyncResult; 
        //throw new Exception("error intorduced here in CallbackDC."); 
        //throw new Exception("service fails"); 
        asyncResult.AddContract = InternalAdd(asyncResult.AddContract); 
    } 
    catch (Exception ex) 
    { 
        asyncResult.Exception = ex; 
        //ErrorInfo err = new ErrorInfo(ex.Message, "CallbackDC faills"); 
        //throw new FaultException<ErrorInfo>(err, "reason goes here."); 
    } 
    finally 
    { 
        asyncResult.Complete(); 
    } 
}

To take care of such exceptions, we can add an Exception property in our AsyncResult and populate this property if an exception occurs in the callback method in the service. Then you can check this Exception property in the relevant End method. FaultException can be raised from this End method.

C#
public AddDataContract EndAddDC(IAsyncResult ar) 
{ 
    AddDataContract result = null; 
    try 
    { 
        //throw new Exception("error intorduced here in EndAddDC."); 
        if (ar != null) 
        { 
            using (AddAsyncResult asyncResult = ar as AddAsyncResult) 
            { 
                if (asyncResult == null) 
                throw new ArgumentNullException("IAsyncResult parameter is null."); 
                if (asyncResult.Exception != null) 
                throw asyncResult.Exception; 
                asyncResult.AsyncWait.WaitOne(); 
                result = asyncResult.AddContract; 
            } 
        } 
    } 
    catch (Exception ex) 
    { 
        ErrorInfo err = new ErrorInfo(ex.Message, "EndAddDC faills"); 
        throw new FaultException<ErrorInfo>(err, "reason goes here."); 
    } 
    return result; 
}

At the client side, you can catch all the exceptions in the client's callback method.

Points to be remember

  1. Declare two relevant method: one as BeginMethodName and another as EndMethodName.
  2. Add [OperationContract(AsyncPattern = true)] to the relevant Begin method.
  3. Do not provide the OperationContract attribute to the corresponding End method.
  4. In the service implementation, the actual processing should go in a method, and this method should run on a separate thread. To make sure this, use ThreadPool.QueueUserWorkItem.
  5. On the client side, call the relevant Begin method, but do not close the proxy after this because the End method needs the same channel to get the result. You can close it in the client's callback after calling the End method.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)