Question

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!

Was it helpful?

Solution 2

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();
}

OTHER TIPS

First, I think that, rather than asking for help in how to make it synchronous, it would be best if you just told us what it is you're trying to do so we could suggest a proper approach to do it.

You don't show your MethodHandler::Retrieve method, but I can guess pretty much what it looks like and it's not even the real problem. I have to tell you in the nicest possible way that this is a really bad idea. It will simply never scale. This would only work with a single SignalR server instance because you're relying on machine specific resources (e.g. kernel objects behind the ManualResetEvent) to provide the blocking. Maybe you don't need to scale beyond one server to meet your requirements, but this still a terrible waste of resources even on a single server.

You're actually on the right track with the client calling back with the requestId as a correlating identifier. Why can't you use that correlation to resume logical execution of whatever process you are in the middle of on the server side? That way no resources are held around while waiting for the message to be delivered to the client, processed and then the follow up message, GetValueResult in your sample, to be sent back a the server instance.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top