I want to make a WCF timer service where clients can register in order to get called back from the service after a certain time has passed. The problem is that the client doesn’t get called back. No Exception is thrown.
The callback interface is:
[ServiceContract]
public interface ITimerCallbackTarget
{
[OperationContract(IsOneWay = true)]
void OnTimeElapsed(int someInfo);
}
The service looks like:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Single)]
public class TimerService : ITimerService
private readonly Timer _timer = new Timer(2000); //System.Timers.Timer
public void Subscribe()
{
ITimerCallbackTarget listener =
OperationContext.Current.GetCallbackChannel<ITimerCallbackTarget>();
_timer.Elapsed += (p1, p2) =>
{
listener.OnTimeElapsed(999);
};
_timer.Start();
}
The callback method used by the client is:
private class TimerCallbackTarget : ITimerCallbackTarget
{
public void OnTimeElapsed(int someInfo)
{
Console.WriteLine(someInfo);
}
}
The client registers like this:
private static void TestTimerService()
{
InstanceContext callbackInstance = new InstanceContext(new TimerCallbackTarget());
using (DuplexChannelFactory<ITimerService> dcf =
new DuplexChannelFactory<ITimerService>(callbackInstance,
"TimerService_SecureTcpEndpoint"))
{
ITimerService timerProxy = dcf.CreateChannel();
timerProxy.Subscribe();
}
}
If I use a different thread at the subscribe method without Timer it works:
ThreadPool.QueueUserWorkItem(p =>
{
listener.OnTimeElapsed(999);
});
It even works with the Timer (for three seconds) if I put a Thread.Sleep(3000) at the end of the subscribe method so my guess is that maybe the channel to the callback-object gets closed after the subscribe method is finished. Using a class-scope variable for the callback object retrieved with OperationContext.Current.GetCallbackChannel(); instead of the method-scope variable didn’t help.
Previously i tried creating new Threads in the elapsed event handler of the Timer of the timer service to make it faster. An ObjectDisposedException was thrown with the message: “Cannot access a disposed object. Object name: ‘System.ServiceModel.Channels.ServiceChannel”. I then tried to simplify my service and found that even using only the Timer causes problems as described but I guess the exception indicates that somewhere the connection to the client’s callback object is lost. It’s strange that there is no excepiton if I don’t make new threads in the Timer thread. The callback method just isn’t called.
In a duplex binding the lifetime of the two channels are linked. If the channel to the TimerService closes, then the callback channel to the CallbackTarget closes too. If you try to use a channel that was closed, you can get an ObjectDisposedExcpetion. In your case this is bad, because you don’t want to keep the Subscribe() channel open just to receive OnTimeElasped() calls… and I’m assuming you want to subscribe for an infinitely long time.
A duplex channel is trying to make your life easier, but doesn’t fit your needs. Behind the scenes a duplex channel is actually creating a second WCF service host for the CallbackTarget. If you create the client’s service host manually to receive callbacks, then you can manage its lifetime independently of the Subscribe() channel.
Below is a fully functional command line program that demonstrates the idea:
Note that no channel is left open longer than needed to make a single call.
Standard disclaimer: This is intended to show how to create “duplex like” behavior. There’s a lack of error handling and other short cuts.