The
WCF service operations can be implemented asynchronously or
synchronously. Clients can consume service operations synchronously
or asynchronously.
There
are three ways to implement asynchronous operations
- IAsyncResult asynchronous
- Task-based asynchronous
- Event-based asynchronous
Here
sample code is given for
IasyncResult asynchronous operation.
Service Implementation
Asynchronous
operations require Begin and End methods.
using System;
using System.ServiceModel;
namespace WCF
{
[ServiceContract]
public interface IMessageService
{
[OperationContract(AsyncPattern
= true)]
IAsyncResult BeginGetMessage(string message, AsyncCallback callback, object state);
string EndGetMessage(IAsyncResult asyncResult);
}
}
In
the service contract, the BeginGetMessage method
takes a parameter, a callback object, and a state object, and returns
an IasyncResult, while EndGetMessage method
takes an IasyncResult and
returns the value. Since the service operation returns IasyncResult,
hence needs to be implemented first.
using System;
using System.Threading;
namespace WCF
{
public class AsyncResult : IAsyncResult, IDisposable
{
private AsyncCallback _callback;
private object _state;
private ManualResetEvent _manualResetEvent;
public AsyncResult(AsyncCallback callback, object state)
{
_callback
= callback;
_state
= state;
_manualResetEvent
= new ManualResetEvent(false);
}
public bool IsCompleted
{
get { return _manualResetEvent.WaitOne(0, false);
}
}
public WaitHandle AsyncWaitHandle
{
get { return _manualResetEvent;
}
}
public object AsyncState
{
get { return _state;
}
}
public ManualResetEvent AsyncWait
{
get { return _manualResetEvent;
}
}
public bool CompletedSynchronously
{
get { return false;
}
}
public void Completed()
{
_manualResetEvent.Set();
if (_callback
!= null)
_callback(this);
}
public void Dispose()
{
_manualResetEvent.Close();
_manualResetEvent
= null;
_state
= null;
_callback
= null;
}
}
}
AsyncResult implements IasyncResult that
has some properties like AsyncCallback, state object and
ManualResetEvent, which handle waiting for asynchronous
operation. IsCompleted is
used to specify that the operation is completed. In
the Complete() method
ManualResetEvent has been set, which means that the event is signaled
now and any other awaiting thread can follow.
For
MessageServiceOperation, MessageAsyncResult
is there which
just inherit from AsyncResult to add Message property.
public
class
MessageAsyncResult
: AsyncResult
{
public
string
Message { get;
private
set;
}
public
MessageAsyncResult(string
message, AsyncCallback
callback, object
state)
:
base(callback,
state)
{
Message
= message;
}
}
Now
implementing the service operations
public class MessageService : IMessageService
{
public IAsyncResult BeginGetMessage(string message, AsyncCallback callback, object state)
{
var replyMessage
= string.Format("{0}{1}", "Server
says :",
message);
var messageAsyncResult
= new MessageAsyncResult(replyMessage,
callback, state);
ThreadPool.QueueUserWorkItem(CompleteProcess,
messageAsyncResult);
return messageAsyncResult;
}
private void CompleteProcess(object state)
{
var messageAsyncResult
= state as MessageAsyncResult;
messageAsyncResult.Completed();
}
public string EndGetMessage(IAsyncResult asyncResult)
{
var messageAsyncResult
= asyncResult as MessageAsyncResult;
messageAsyncResult.AsyncWait.WaitOne();
return messageAsyncResult.Message;
}
}
In
the BeginGetMessage () method
an instance of MessageAsyncResult
is created by
passing appropriate parameters to it then
call ThreadPool.QueueUserWorkItem() method to add method to
waiting queue for execution. The method CompleteProcess()
will run whenever a thread becomes available and it finally call
the Completed()
method to release the thread and allow the other operations to
execute. In the EndGetMessage () method, In
WaitOne() method, wait for ManualResetEvent until the current
execution ends then return the Result property.
Service
Hosting
Here
console application will be used as a server, to host service.
class Program
{
static void Main(string[]
args)
{
var svcHost
= new ServiceHost(typeof(MessageService));
Console.WriteLine("Available
Endpoints :\n");
svcHost.Description.Endpoints.ToList().ForEach(endpoints
=>Console.WriteLine(endpoints.Address.ToString()));
svcHost.Open();
Console.ReadLine();
}
}
And
the configuration for that,
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="WCF.Asynchronous.Services.MessageService">
<endpoint address="" binding="basicHttpBinding" contract="WCF.Asynchronous.Services.IMessageService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost/WCF.Asynchronous.Services/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
Client
Here
is given a simple console app to consume this service,
public class Program
{
static void Main(string[]
args)
{
var channelFactory
= new ChannelFactory<IMessageService>("MessageServiceEndpoint");
var proxy
= channelFactory.CreateChannel();
var message
= Console.ReadLine();
while (message.Equals("exit", StringComparison.CurrentCultureIgnoreCase)
== false)
{
proxy.BeginGetMessage(message,
ClientCallBack, proxy);
message
= Console.ReadLine();
}
}
private static void ClientCallBack(IAsyncResult ar)
{
var res
= ar.AsyncState as IMessageService;
if (res
!= null)
{
var message
= res.EndGetMessage(ar);
Console.WriteLine(message);
}
}
}
And
the configuration file for this is,
<configuration><bindings />
<client>
<endpoint address="http://localhost/WCF.Asynchronous.Services/"
binding="basicHttpBinding" contract="WCF.Asynchronous.Services.IMessageService"
name="MessageServiceEndpoint">
</endpoint>
</client>
</system.serviceModel>
</configuration>