OneWay vs Async in WCF

Download the sample code here: http://milestonetg.com/examples/OneWayServiceExample.zip

On a recent project, there was some debate about the behavior of OneWay OperationContracts, so I thought I'd put together a little example.

Specifically, the debate centered around when control is returned back to the calling routine. Let's take a look at the MSDN docs...

http://msdn.microsoft.com/en-us/library/system.servicemodel.operationcontractattribute.isoneway(v=vs.110).aspx

"Use the IsOneWay property to indicate that an operation does not return a reply message. This type of operation is useful for notifications or event-style communication, especially in two-way communication. "

"Specifying that an operation is a one-way operation means only that there is no response message. It is possible to block if a connection cannot be made, or the outbound message is very large, or if the service cannot read inbound information fast enough. If a client requires a non-blocking call, generate AsyncPattern operations."

I also encourage you to read the MSDN entry on OneWay Services:

http://msdn.microsoft.com/en-us/library/ms730149(v=vs.110).aspx

"It is important to realize that while some one-way applications return as soon as the outbound data is written to the network connection, in several scenarios the implementation of a binding or of a service can cause a WCF client to block using one-way operations. In WCF client applications, the WCF client object does not return until the outbound data has been written to the network connection. This is true for all message exchange patterns, including one-way operations; this means that any problem writing the data to the transport prevents the client from returning. Depending upon the problem, the result could be an exception or a delay in sending messages to the service."

A key difference between OneWay and Async is the thread in which the call is made.  With OneWay, the call is made on the same thread as the calling routine, so it IS, as the docs point out, a blocking operation.  However, OneWay only waits for a receipt acknowledgement (ACK) from the service that the request was received--it does not wait for the service to finish processing.  Under ideal conditions, there is no wait, and if the caller doesn't care about whether the service was successful, this approach can actually be more performant than an async call.

Async calls happen on a separate background worker thread from the calling routing.  This means that there is no blocking at all (well, almost not at all... we'll take a look at this a bit later)--the call, the wait for the ACK, and wait for response, happens on the background thread.

Let's look at a simple experiment.

Let's take a simple service that simulates some work:

[ServiceContract]
    public interface IExampleService
    {
        [OperationContract]
        long DoWork();

        [OperationContract(IsOneWay = true)]
        void DoOneWayWork();

    }

Then, a simple client that makes calls to the servers and tracks the timing:

class Client
    {
        static void Main(string[] args)
        {
            using (Services.ExampleServiceClient proxy = new ExampleServiceClient())
            {
                Stopwatch sw = new Stopwatch();

                sw.Start();

                try
                {
                    proxy.DoWork();

                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message + " ");
                }
                
                sw.Stop();

                Console.WriteLine("proxy.DoWork(): Elapsed Time: {0}ms\n", sw.ElapsedMilliseconds);

                sw.Reset();

                sw.Start();

                try
                {
                    proxy.DoOneWayWork();

                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message + " ");
                }

                sw.Stop();

                Console.WriteLine("proxy.DoOneWayWork(): Elapsed Time: {0}ms\n", sw.ElapsedMilliseconds);

                sw.Reset();

                sw.Start();

                try
                {
                    proxy.DoWorkAsync();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message + " ");
                }

                sw.Stop();

                Console.WriteLine("proxy.DoWorkAsync(): Elapsed Time: {0}ms\n", sw.ElapsedMilliseconds);

                sw.Reset();
            }

            Console.ReadLine();

        }
    }


Scenario 1: Happy Service, Happy Client

If all is well, we can see that calls to the service with OneWay and Async are fast.  Note that OneWay is actually faster in this case.  Something to consider if you're looking for "fire-and-forget", but still want to know that the service was reachable.

As expected, the synchronous call blocks for as long as the service is doing its work, plus communication overhead.

Scenario 2: Down Service

Now let's take a look at the behavior if the service is down, or the endpoint is otherwise unreachable.


Notice that the OneWay Service, as the docs highlight, waits for the ACK and blocks for 2 seconds while the call is attempted and the exception generated.

The Async, however, because the communication attempt is on a different thread, there is no blocking of the calling thread.

Scenario 3: Busy Service

What if the service is reachable, but all available connections are used, and our request has to wait in the queue for processing.

For testing, we'll throttle the connections to 1, and spin up two clients.

 <serviceThrottling maxConcurrentCalls="1" maxConcurrentInstances="1" maxConcurrentSessions="1"/>


If the service is busy, as expected based on our readings on MSDN, the OneWay service call blocks while it waits for that receipt acknowledgement from the service.

Conclusion

As we can see from our readings on MSDN and this little experiment, OneWay does block execution of the calling thread.  How long it blocks depends on the scenario. If everything is hunky-dory, the block is negligible, and could even be faster than an async call.  If there is an issue somewhere along the way, however, it behaves just like our standard two-way synchronous calls.


Comments

Popular posts from this blog

Adding New Microsoft Extensions to Legacy WCF and ASMX Web Services

Using NHibernate in Asp.Net Core

Code Coverage for Multiple Projects in a Single Build using Dotnet Test and Coverlet