Prerequisites for Implementing a Keep-Alive Mechanism in WCF 3.0
There seems to be a common misconception that, in order to find out whether a WCF session is still alive, one has to implement some kind of custom ping or heartbeat operation on the service, which is to be regularly called by the client application. However, the WCF framework, when configured correctly, already does this for you in the background.
The key to this feature is the ReliableSession
class. When ReliableSession.Enabled
is true, WCF will continuously generate internal infrastructure messages to check whether the service is still ready. After the service has been dead for some specified time, the proxy's ClientBase<TChannel>.State
is automatically set to CommunicationState
.Faulted
, and the ICommunicationObject.Faulted
event is raised, giving the client application a chance to react. The delay between the service breakdown and the Faulted event can be specified through the ReliableSession.InactivityTimeout
. If, for instance, the InactivityTimeout
is set to four minutes, WCF will generate several infrastructure messages during every four minutes. Therefore, if you shut down the service, the Faulted event will be raised on the client after four to six minutes. If you set the InactivityTimeout
to 20 seconds, it will be raised after 20 to 30 seconds. The exact duration depends on WCF-internal implementation details.
In order for this to work, you also have to set the Binding.ReceiveTimeout
to at least the duration of the InactivityTimeout
. The ReceiveTimeout
specifies how long the service will stay alive without any service operations being called from the client application. If, for instance, you expect your client application to call service operations at least twice an hour, you may set the ReceiveTimeout
to 30 minutes, but still let the InactivityTimeout
be 4 minutes or so, in order that the client application can take measures in the background when the service is temporarily interrupted.
The following example shows how to define both timeouts on the client and server sides, either configuratively (in the App.config file), or imperatively (in code). The example presupposes that you are using a reliable session-capable binding (such as WSHttpBinding
or NetTcpBinding
) and that you have applied the ServiceContractAttribute.SessionMode
, ServiceBehaviorAttribute.InstanceContextMode
, OperationContractAttribute.IsInitiating
, and OperationContractAttribute.IsTerminating
properties in meaningful ways. If you are not sure about that, you may want to read the respective MSDN documentation entries first. A comparison table of the system-provided bindings can be found here. Furthermore, if you do not like hand-coding configuration files, you can use the WCF service Configuration Editor Tool, which is installed with the Microsoft Windows SDK.
<configuration>
<system.serviceModel>
<!-- In the App.config files of the service host and client apps: -->
<bindings>
<netTcpBinding>
<binding name="myNetTcpBinding" receiveTimeout="00:30:00">
<reliableSession enabled="true" inactivityTimeout="00:04:00"/>
</binding>
</netTcpBinding>
</bindings>
<!-- In the App.Config file of the service host app: -->
<services>
<service name="MyTest.MyService">
<endpoint
address="net.tcp://localhost/MyService/"
binding="netTcpBinding"
bindingConfiguration="myNetTcpBinding"
contract="MyTest.IMyService"/>
</service>
</services>
<!-- In the App.Config file of the client app: -->
<client>
<endpoint
address="net.tcp://localhost/MyService/"
binding="netTcpBinding"
bindingConfiguration="myNetTcpBinding"
contract="MyTest.IMyService"/>
</client>
</system.serviceModel>
</configuration>
// using System;
// using System.ServiceModel;
// In the service host and client apps:
var myNetTcpBinding = new NetTcpBinding();
myNetTcpBinding.ReceiveTimeout = new TimeSpan(0, 30, 0);
myNetTcpBinding.ReliableSession.Enabled = true;
myNetTcpBinding.ReliableSession.InactivityTimeout = new TimeSpan(0, 4, 0);
// In the service host app:
var host = new ServiceHost(typeof(MyService));
host.AddServiceEndpoint(typeof(IMyService), myNetTcpBinding, "net.tcp://localhost/MyService");
// In the client app:
var factory = new ChannelFactory<IMyService>(myNetTcpBinding, "net.tcp://localhost/MyService");
var channel = factory.CreateChannel();