WCF Callback Deadlocks Even With 'UseSynchronizationContext = false'

521 Views Asked by At

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 to Test1 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.

0

There are 0 best solutions below