I am stuck with a problem that I can't quite understand.
The problem is related to synchronization between threads on the client side but I can't find the root cause of this. I created a small demo to simulate the problem, so it will be easier to explain.
Service Contact:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ITestServiceCallback))]
public interface ITestService
{
[OperationContract]
Task RegisterAsync();
[OperationContract]
Task UnRegisterAsync();
[OperationContract]
int Test1(int i);
}
Callback Contract:
public interface ITestServiceCallback
{
[OperationContract(IsOneWay = true)]
void TestCallback(int value);
}
Service Implementation:
The service upon calling Test1
method will raise the callback with the value supplied to Test1
method.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, UseSynchronizationContext = false, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class TestService : ITestService
{
private readonly HashSet<ITestServiceCallback> _callbacks = new HashSet<ITestServiceCallback>();
public Task RegisterAsync()
{
Console.WriteLine("Registering Callback, Thread: {0}", Thread.CurrentThread.ManagedThreadId);
var callbackProxy = OperationContext.Current.GetCallbackChannel<ITestServiceCallback>();
_callbacks.Add(callbackProxy);
return Task.CompletedTask;
}
public Task UnRegisterAsync()
{
Console.WriteLine("Unregistering Callback, Thread: {0}", Thread.CurrentThread.ManagedThreadId);
var callbackProxy = OperationContext.Current.GetCallbackChannel<ITestServiceCallback>();
bool removed = _callbacks.Remove(callbackProxy);
Console.WriteLine("Callback was{0} successfully removed", removed ? "" : " not");
return Task.CompletedTask;
}
public int Test1(int i)
{
Console.WriteLine("Working on server, Test1, Thread: {0}", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(500);
raiseCallback(i);
Console.WriteLine("Finished on server, Test1, Thread: {0}", Thread.CurrentThread.ManagedThreadId);
return i;
}
private void raiseCallback(int value)
{
foreach (var callback in _callbacks)
{
try
{
callback.TestCallback(value);
}
catch (Exception ex)
{
Console.WriteLine("Callback calling failed in server: {0}", ex);
}
}
}
}
The Client Code:
The client is first registering to the server's callbacks (note the async register call - This is the problem)
Then calls Test1
.
here the call to Test1
never returns
var callBack = new ClientCallback();
var client = DuplexChannelFactory<ITestService>.CreateChannel(callBack, _defaultBinding, _defaultEndpointAddress);
using (client as IDisposable)
{
await client.RegisterAsync(); // client.RegisterAsync.Wait();
Console.WriteLine("Client Thread: {0}", Thread.CurrentThread.ManagedThreadId);
try
{
int res = client.Test1(5);
Console.WriteLine("Test1 was called, Server Result: {0}", res);
}
finally
{
await client.UnRegisterAsync();
}
}
[CallbackBehavior(UseSynchronizationContext = false)]
class ClientCallback : ITestServiceCallback
{
public void TestCallback(int value)
{
Console.WriteLine("Callback Called, Value: {0}, Thread: {1}", value, Thread.CurrentThread.ManagedThreadId);
}
}
- When calling
RegisterAsync
synchronously (using.Wait()
) the call toTest1
does not deadlock. - The call for
Test1
passes in the server without any problem. The binding used is NetNamedPipeBinding:
Binding _defaultBinding = new NetNamedPipeBinding { MaxBufferSize = int.MaxValue, MaxReceivedMessageSize = int.MaxValue, ReceiveTimeout = TimeSpan.MaxValue, SendTimeout = TimeSpan.MaxValue, CloseTimeout = TimeSpan.MaxValue };
I thought that when setting UseSynchronizationContext
property to false
(in both the ServiceBehaviourAttribute
and in CallbackBehaviourAttribute
) should solve this issue.