I'm doing some WCF experiments to understand how far I can run with sessions, callbacks and TPL. One of these experiments has strange outcomes... The service is implemented with a bidirectional binding (NetTcpBinding) and the notifications are implemented as callbacks. The expected behavior should be:
- The client calls a method on the service (Do) in a sync way
- The service calls a callback on the client in a sync way
- The client (in the callback) calls a second a method on the service in an async way
The service requires a session, the InstanceContext is PerSession and the concurrency mode is Single. In this situation, I know that, in the context of a callback, I can't call any method of the service (on the same client) in a sync way so the code can be:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(INotificationCallback))]
public interface INotificationService
{
[OperationContract]
int Do(int value);
[OperationContract]
int ReDo(int value);
}
public interface INotificationCallback
{
[OperationContract(IsOneWay = true)]
void Notify(int value);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Single)]
public class NotificationService : INotificationService
{
public int Do(int value)
{
var notificationCallback = OperationContext.Current.GetCallbackChannel<INotificationCallback>();
//Task.Run(() => notificationCallback.Notify(value * 2));
notificationCallback.Notify(value * 2);
Thread.Sleep(1000);
return value * 2;
}
public int ReDo(int value)
{
return value + 1;
}
}
The implementation of the callback interface on the client:
class CallbackInstance : INotificationServiceCallback
{
public NotificationServiceClient Client { get; set; }
#region Implementation of INotificationServiceCallback
public void Notify(int value)
{
Console.WriteLine("Notified: {0}", value);
Task<int> task = Client.ReDoAsync(value);
task.ContinueWith(t => Console.WriteLine("In Notify - ReDo({0}): {1}", value, t.Result));
}
#endregion
}
The main method that runs all the code:
static void Main(string[] args)
{
CallbackInstance callback = new CallbackInstance();
NotificationServiceClient client = new NotificationServiceClient(new InstanceContext(callback));
callback.Client = client;
int result = client.Do(10);
Console.WriteLine("Do({0}): {1}", 10, result);
Console.ReadLine();
}
In my thougths the lines inside the implementation of the Notify method:
Task<int> task = Client.ReDoAsync(value);
task.ContinueWith(t => Console.WriteLine("In Notify - ReDo({0}): {1}", value, t.Result));
should break the deadlock but... no, when the ReDoAsync is called an exception is thrown saying "This operation would deadlock because the reply cannot be received until the current Message completes processing...". Yes, dear, I know. Infact I thought to call back the service in an async way in order to bypass the problem, and it's not working.
But what drives me crazy is the fact that changing a bit the code make it work like a charm; instead of the ReDoAsync call I tried with:
Task<int> task = Task.Run(() => Client.ReDo(value));
AND IT WORKS! So the question is: don't the Async version of the client methods are supposed to make a call identical or similar to the one that works? If not, what really the Async version of the client methods do?
Thanks.