I’ve recently encountered some confusion around the behavior of one-way operations in WCF that I’m going to try and clear up. In particular, developers are under the impression that a one-way operation == a non-blocking call. However, this is not necessarily the case. A one-way operation means that we will call the underlying channel in a "one-way manner". For IOutputChannel/IDuplexChannel, this maps to channel.Send(). For IRequestChannel this maps to channel.SendRequest() followed by a check for a null response.
Now, sometimes the underlying channel can complete immediately (UDP will drop the packet if the network is saturated, TCP may copy bytes into a kernel buffer if there’s room, etc). However, depending on the amount of data transmitted and the number of simultaneous calls to a proxy, you will often see blocking behavior. HTTP, TCP, and Pipes all have throttling built into their network protocols.
If this isn’t desirable, there are a few alternatives depending on your application design. First off, if you want a truly non-blocking call, you should call channel.BeginSend/client.BeginXXXX (i.e. generate an async proxy). This is step one if you want non-blocking calls. With an asynchronous proxy, we will always be non-blocking from a thread perspective (which is always my recommendation for middle tier solutions, though there’s some quota coordination necessary to avoid flooding outgoing sockets).
For one-way operations, when your async callback is signaled it means that the channel stack has "successfully put the message on the wire". When this happens depends on your channel stack:
- TCP signals when the socket.[Begin]Send() of the serialized message has completed (either because it’s been buffered by the kernel or put onto the NIC)
- Pipes are a similar process (NT Named Pipes work similarly to TCP under the covers but without the packet loss)
- MSMQ signals when the Message has been transferred successfully to the queue manager
- HTTP signals when we’ve received an empty response. The only other alternative would be to remove all guarantees (and have to arbitrarily propagate the exception through some other call or thread). Trust me, this is better
- UDP will complete when the UDP socket send completes (which is effectively instantaneous)
For two-way operations, when your async callback is signaled it means that the channel stack has "successfully put the message on the wire and then received a correlated response".
Asynchronous operations can be tricky, and can often get you into flooding trouble when used incorrectly. So be careful, use quotas to manage your flow control, and always remember that the internet is not a big truck; it is a series of tubes and sometimes they get clogged. And you shouldn’t try to fight the clogs by pouring more data down the tubes 🙂