What Is Duplex Communication
Duplex communication lets a WCF service call back into the client on its own initiative, rather than only replying to requests. This requires declaring a CallbackContract on the ServiceContract, which specifies an interface the client must implement and expose to the service. On the server side, the operation implementation calls OperationContext.Current.GetCallbackChannel<T>() to obtain a proxy back to that specific client's callback instance and invoke methods on it directly, effectively turning the client into a mini service for the duration of the session.
Cricket analogy: Getting a callback channel is like a broadcaster handing the on-field reporter a direct line to the studio so the studio can cut to them unprompted, instead of only answering questions the reporter asks — the studio can now initiate contact.
Configuring the Callback Contract
The callback interface is wired up by setting CallbackContract on the ServiceContract attribute, for example [ServiceContract(CallbackContract = typeof(IChatCallback))], and its methods are typically marked IsOneWay = true since the server usually doesn't need a reply from a notification push. On the client, you cannot use a plain ChannelFactory<T> as with ordinary services; instead you construct a DuplexChannelFactory<T> and pass an InstanceContext that wraps an object implementing the callback interface, so incoming calls from the server are dispatched to that object's methods.
Cricket analogy: Declaring the callback interface is like a team pre-registering exactly which staff member the broadcaster is allowed to interrupt during play — only that designated liaison can receive live updates, nobody else on the bench.
Session and Instance Management
Duplex only works over sessionful bindings because the service needs a durable channel back to a specific client instance: WSDualHttpBinding achieves this over HTTP using two separate one-way channels (the client's callback endpoint must be reachable at its own address), while NetTcpBinding keeps a single persistent TCP connection open in both directions, which avoids the NAT/firewall problems of the dual-HTTP approach. Because of this, services are usually configured with InstanceContextMode.PerSession so each connected client gets its own service instance that can track and reuse its callback channel — for broadcast scenarios, the service typically stores active callback channels in a thread-safe collection, such as a ConcurrentDictionary keyed by a client or subscription ID.
Cricket analogy: Keeping a persistent session per client is like a stadium assigning each broadcaster a dedicated radio channel for the whole match rather than reconnecting on every over — NetTcpBinding is the single open line, while WSDualHttpBinding is more like using two separate walkie-talkies, one for sending and one for receiving.
Reliability, Threading and Pitfalls
Callback invocations execute on a client-side ThreadPool thread rather than the UI thread, so a WPF or WinForms client must marshal any UI updates through Dispatcher.Invoke or Control.Invoke before touching bound controls. If the client disconnects or its proxy faults, calling a stale callback channel throws a CommunicationObjectFaultedException (or a general CommunicationException), so services holding onto callback references for broadcast should catch these, remove the dead subscriber, and continue rather than letting one bad channel take down the notification loop. A related pitfall is deadlock: if a client call into the service is still in progress and the service tries to call back into that same client synchronously, the default single-threaded ConcurrencyMode can block; ConcurrencyMode.Reentrant is often needed on the service contract to allow that nested callback safely.
Cricket analogy: Forgetting to marshal a callback onto the UI thread is like a runner sprinting straight onto the pitch mid-delivery to update the scoreboard by hand instead of signaling the official scorer — it touches something it isn't allowed to touch directly and things break.
[ServiceContract(CallbackContract = typeof(IChatCallback))]
public interface IChatService
{
[OperationContract]
void Join(string userName);
[OperationContract]
void SendMessage(string text);
}
public interface IChatCallback
{
[OperationContract(IsOneWay = true)]
void OnMessageReceived(string userName, string text);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession,
ConcurrencyMode = ConcurrencyMode.Reentrant)]
public class ChatService : IChatService
{
private static readonly ConcurrentDictionary<string, IChatCallback> Subscribers = new();
public void Join(string userName)
{
var callback = OperationContext.Current.GetCallbackChannel<IChatCallback>();
Subscribers[userName] = callback;
}
public void SendMessage(string text)
{
foreach (var kvp in Subscribers.ToArray())
{
try { kvp.Value.OnMessageReceived("server", text); }
catch (CommunicationException) { Subscribers.TryRemove(kvp.Key, out _); }
}
}
}WSDualHttpBinding requires the client's callback endpoint to be reachable at a real, routable address — problematic behind NAT or restrictive firewalls. NetTcpBinding avoids this entirely because it multiplexes both directions over a single already-established TCP connection, which is why it's the more common choice for duplex on an intranet.
Storing raw callback channel references indefinitely without pruning faulted ones is a common source of memory leaks and noisy exceptions in long-running duplex services. Always wrap calls to a cached callback channel in a try/catch for CommunicationException and ObjectDisposedException, and remove the entry on failure.
- Duplex requires a CallbackContract declared on the ServiceContract and a duplex-capable binding.
- Clients must use DuplexChannelFactory<T> with an InstanceContext wrapping their callback implementation.
- Servers obtain the client's callback channel via OperationContext.Current.GetCallbackChannel<T>().
- NetTcpBinding uses one persistent bidirectional TCP connection; WSDualHttpBinding uses two separate HTTP channels and needs a reachable client endpoint.
- InstanceContextMode.PerSession is typically used so each client's callback channel is tracked against its own service instance.
- Callback methods run on a client ThreadPool thread and must be marshaled to the UI thread manually.
- ConcurrencyMode.Reentrant helps avoid deadlocks when the server calls back into a client mid-request.
Practice what you learned
1. What must be declared on a ServiceContract to enable duplex communication?
2. Which factory class must a duplex client use instead of ChannelFactory<T>?
3. Why is NetTcpBinding often preferred over WSDualHttpBinding for duplex on an intranet?
4. What exception commonly occurs when a service tries to invoke a callback on a client that has disconnected?
5. What is the purpose of ConcurrencyMode.Reentrant in duplex scenarios?
Was this page helpful?
You May Also Like
WCF Message Exchange Patterns
Explains the three message exchange patterns (MEPs) — One-Way, Request-Reply, and Duplex — that define how messages flow between a WCF client and service.
WCF vs Web API
Compares WCF's protocol-flexible, contract-first model against ASP.NET Web API's HTTP-first, resource-oriented approach, and when to choose each.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics