Auto-Open (part 1)

Several objects in WCF support ICommunicationObject.  ICommunicationObjects have a well-defined life-cycle:

    Created -> Opening -> Opened -> Closing -> Closed

There is also a Faulted state and a few more transitions than these, but they are not relevant to auto-open.

Most ICommunicationObjects can only be used in the Opened state.  For example, most channels will throw if you try to Send a Message when the channel is not Opened.

However, for usability, a few objects support auto-open.  This feature allows you to call an I/O method while the object is in the Created (or Opening) state.  The object's I/O methods ensure that the object transitions to the Opened state before calling the inner channel.  Clients generated by svcutil.exe, client channels created by ChannelFactory<T>, and ChannelFactory<T> itself all support auto-open.

Normally this is great.  The channel stack is very explicit about state transitions, which improves debugging and simplifies the contract that channel authors must implement.  At the same time, the user experience is very simple--create a thing, configure it, and go.

There is one interesting implementation detail though, that complicates this picture.  It does not usually cause problems in real applications but does cause confusion among people experimenting with WCF sessions and concurrency.

The short story is that if you are using a session and you need to send multiple messages concurrently and you care that the first couple messages are actually sent concurrently, you should not use auto-open.  Instead, you should explicitly Open your client/channel before using it from multiple threads.

The long story is... longer.

If you use ChannelFactory<T> to create a channel, or new a svcutil-generated client, the top-most layer of the channel stack is ServiceChannel.  ServiceChannel is an internal class that is added automatically by ChannelFactory<T>.  Its main job is translating typed calls to untyped ones.  For example, when you call this:

    channel.SubmitPurchaseOrder(purchaseOrder);

ServiceChannel does some work and then calls this:

    channel.Send(message);

The interesting implementation detail enters because ServiceChannel both supports auto-open and does not know whether its user cares about Message ordering.  "What have these to do with each other?" you ask.  Excellent question--I'm glad you asked.

Let's look at how auto-open could work.  The simplest implementation would just check this.State and call Open if it is Created.  This does not work for multiple threads though, because it may result in multiple Open calls.

One improvement would be to allow the first request to proceed, but then block all subsequent requests until Open returns.  The problem with this approach is that when the first call releases the lock, a second call that came in later may race ahead and call Send on the underlying channel first.  This could happen even if the application uses WS-RM with OrderedSession enabled.  That would be bad, because it would mean ServiceChannel broke RM's guarantee.  One could argue that this is okay, since the user has multiple threads and therefore the order never was deterministic.  However, deterministically ordered async calls can cause the same problem.

To fix this, as long auto-open might be blocking a previous request, ServiceChannel must block each subsequent request until it is sure that all those previous requests have called the inner channel.  The only way to be sure that previous requests have called the inner channel is to wait until the [Begin]{Send|Request} call has returned.  However, ServiceChannel does not have to do this indefinitely.  Once there are no pending calls, there can be no previous blocked calls, so subsequent calls can run freely.

This means that the first couple calls on a channel may get synchronized, but after that things should open up.  If you want to avoid that initial congestion, you can explicitly open the channel.

ServiceChannel could have supported ordering only when the binding says it also supports ordering.  However, it turns out most sessionful bindings support ordering so we would not gain much.  Ideally ServiceChannel could get an explicit statement from the developer (like an attribute property) that says, "Yes, I care about ordering."  There are several problems with that though--one is that this information is most useful on the client but that would require a WCF-specific extension to metadata to get it there.