Please read the an important update here: WCF OneWay – Abort or Not
Following my last post – WCF and OneWay operations – Is OneWay really OneWay
I built up a solution that works against various sets of communication settings – different protocols and binding elements to illustrate how OneWay is inconsistent and how it might affect your code.
The source code is attached, you can download it.
I will describe my findings which address several concepts I illustrate grouped by different bindings.
If you care for the best practices alone just skip this section.
For simplicity sake, let’s assume the O/W operation on the service takes 5 seconds to complete.
Simple Execution Example:
1 IMyServiceAsync channel = Helpers.GetChannel(ChannelMode.PerCallNetTcp) as IMyServiceAsync;
6 channel.Do(false, 0);
The standard BasicHttpBinding works well with O/W invocations. Actually, it works just as you would expect calling O/W operations, full Fire-and-Forget behavior.
This means the O/W operations are non-blocking to the client and gets dispatched asynchronously on the service side, they do not affect consequent R/R calls and closing the channel gracefully doesn’t hang as well.
This is understandable because of the simple nature of BasicHttpBinding and its purpose.
As some of you must know, O/W invocations simply don’t work with the standard OOTB (out of the box) NetTcpBinding.
You must enable ReliableSession if you wish to get a similiar behavior to O/W.
Following are specifics that illustrate the key issues:
- Simple Execution Behavior:
Calling O/W operation right after another on the same proxy (lines 3&4 in the code) –
The client is not blocked, however, the operations on the service side are dispatched synchronously – one after another, not something you would expect.
- Calling a R/R after O/W operation was called – the client will be blocked and the call will not be dispatched to service operation until ALL O/W operations are complete! (Meaning, we need to wait 10seconds until Do gets dispatched to the service operation) – Definitely not something you would expect! (don’t forget point number 1 – the O/W happen synchronously on the service)
- Closing the channel – acts like a R/R operation. Meaning it waits until all previous called O/W operations are complete. (In this case it wouldn’t block because the “Do” operation suffered the waiting. If “Do” hadn’t been called – CloseChannel would block for 10 seconds)
- Aborting the channel – you might consider that in cases where you need to invoke only O/W operations on the proxy (E.g. remove line number ‘6’ from the code), you could abort the channel and thus getting the Fire-and-Forget behavior you wish for. (Abort forcefully terminates the channel)
Well, it’s not good. take point 1 into mind. The calls are dispatched synchronously on the service side.
If you do abort the channel after the 2 O/W calls – the second call might not arrive to the service.
To be more exact, if the service is set with InstanceContextMode PerCall – it will arrive, with PerSession it will not! (This is understandable though, since the session instance is gone and the call can’t be dispatched to the session instance any more)
Obviously, your code shouldn’t rely on what was set on the service side in that matter, so it’s not recommended.
- Exception during O/W service invocation
We need to distinguish between FaultExceptions which correspond to Fault messages and normal exceptions which also faults the channel.
Throwing a fault exception from the service will not have any affect. However, throwing an exception from a O/W operation will fault the channel – if the client makes additional R/R calls to the service or tries to close the channel gracefully – blows up, an exception is thrown.
This is obviously problematic, how would the client know whether he can reuse the channel or not? How can it know if and when exception was raised from non-blocking O/W operation? These are key issues that will be addressed later.
- Customize the Binding – TCP transport with OneWay binding element
This would be preferred when O/W contracts are in place. (All operations are O/W)
It acts just life Fire-and-Forget semantics as you might expect. Plus, no need for Reliable Session.
However, you should note that PerSession is not supported. (each call terminates the session)
Some of these you might expect due to the nature of duplex style of communications that NetTcp possesses. But still, you want your code to work against O/W operations as it should, so bear with me 🙂
The standard WsHttpBinding also behaves differently when it comes to O/W operation invocations.
In short, calling the first O/W operation doesn’t block and the code continues the execute but every other consequent call will be pending until the O/W operation completes – be it R/R, O/W, or Closing the channel.
Exceptions raised from O/W operation fault the channel as well in this case, same as NetTcp.
The behavioral inconsistency does have understandable explanations to why this might occur. However, the main goal is to write code that simply works apart from the communication chosen behind the scenes.
Client Side OneWay Invocation Best Practices
Fortunately, there is an invocation pattern that utilizes the Fire-and-Forget semantics for all of the modes.
(It is very similiar in concept to the basicHttp / customized tcp transport with one way binding element)
The idea is as follows – Calling O/W operation should end the use of your proxy.
IMyService proxy1 = GetNewProxy();
IMyService proxy2 = GetNewProxy();
IMyService proxy3 = GetNewProxy();
IMyService proxy4 = GetNewProxy();
IMyService proxy5 = GetNewProxy();
Service Design Guidelines
- Don’t define OneWay operations on a sessionful contract, it implies bad design. (Session matters, client should know if something happened)
- If you must – then set it as a terminating operation – expect & force client to create a new session after invoking O/W operations.
- If you managed to design O/W contracts (all operations are O/W) – prefer exposing it using a custom binding with the desired transport and a OneWay binding element.
- Prefer throwing FaultException instead of normal exceptions (especially in O/W operations, but a good thing in general). You might consider promoting exceptions by implementing IErrorHandler.
I stated in the last post, O/W can still yield exception and block, so be aware of that. (If you follow the practice, it will be because there is an actual communication problem, not because of O/W behavior inconsistency)
Source code – feel free to download the source code and play around with it. The main samples are located in the method “OneWayShowCases” (activate a different sample as you wish).
The source code contains another part which I will discuss soon – using asynchronous invocation appropriately.
** UPDATE **:
You shouldn’t abort the channel after calling O/W operation since it may prevent the service call from being accepted altogether.
Instead, you should close the channel gracefully, you should keep in mind that the ‘Close’ method can block, so if you care about that, I recommend using the asynchronous BeginClose/EndClose.