Search code examples
asynchronous.net-4.0signalrsynchronousmanualresetevent

synchronously invoke client side method with SignalR


SignalR does not have the ability to have client methods which returns a value. So I am trying to create a helper class to make this possible.

So this is what I am trying to do:

  1. Server side: Call client method and provide unique request id Client(clientId).GetValue(requestId)
  2. Server side: Save requestId and wait for answer using ManualResetEvent
  3. Client side: Inside void GetValue(Guid requestId) call server method hubProxy.Invoke("GetValueFinished", requestId, 10)
  4. Server side: find waiting method by requestId => set return value => set signal
  5. Server side: Method not longer waiting vor ManualResetEvent and returns retrieved value.

I am able to get it work unfortunately. Here is my code:

public static class MethodHandler
{
    private static ConcurrentDictionary<Guid, ReturnWaiter> runningMethodWaiters = new ConcurrentDictionary<Guid,ReturnWaiter>();

    public static TResult GetValue<TResult>(Action<Guid> requestValue)
    {
        Guid key = Guid.NewGuid();
        ReturnWaiter returnWaiter = new ReturnWaiter(key);
        runningMethodWaiters.TryAdd(key, returnWaiter);
        requestValue.Invoke(key);
        returnWaiter.Signal.WaitOne();
        return (TResult)returnWaiter.Value;
    }

    public static void GetValueResult(Guid key, object value)
    {
        ReturnWaiter waiter;
        if (runningMethodWaiters.TryRemove(key, out waiter))
        {
            waiter.Value = value;
        }
    }
}

internal class ReturnWaiter 
{
    private ManualResetEvent _signal  = new ManualResetEvent(false);
    public ManualResetEvent Signal { get { return _signal; } }
    public Guid Key {get; private set;}

    public ReturnWaiter(Guid key)
    {
        Key = key;
    }

    private object _value;
    public object Value 
    {
        get { return _value; }
        set 
        {
            _value = value;
            Signal.Set();
        }
    }
}

Using this MethodHandler class I need to have two method server side:

public int GetValue(string clientId)
{
    return MethodHandler.GetValue<int>(key => Clients(clientId).Client.GetValue(key));
}

public void GetValueResult(Guid key, object value)
{
    MethodHandler.GetValueResult(key, value);
}

Client side implementation is like this:

// Method registration
_hubProxy.On("GetValue", new Action<Guid>(GetValue));

public void GetValue(Guid requestId)
{
    int result = 10;
    _hubConnection.Invoke("GetValueResult", requestId, result);
}

PROBLEM:
if I call server side GetValue("clientid"). The client method will not be invoked. If I comment out returnWaiter.Signal.WaitOne();, client side GetValue is called and server side GetValueResult is called. But of course this time the method has already returned.

I thought is has to do with the ManualResetEvent but even using while(!returnWaiter.HasValue) Thread.Sleep(100); will not fix this issue.

Any ideas how to fix this issue?

Thanks in advance!


Solution

  • Problem solved:

    The problem only occured in Hub.OnConnected and Hub.OnDisconnected. I don't have an exact explanation why, but probably these methods must be able to finish before it will handle your method call to the client.

    So I changed code:

    public override Task OnConnected()
    {
        // NOT WORKING
        Debug.Print(MethodHandler.GetValue<int>(key => Clients(Context.ConnectionId).Client.GetValue(key)));
    
        // WORKING
        new Thread(() => Debug.Print(MethodHandler.GetValue<int>(key => Clients(Context.ConnectionId).Client.GetValue(key)))).Start();
    
        return base.OnConnected();
    }