使用SignalR同步调用客户端方法

时间:2013-07-12 16:02:39

标签: asynchronous .net-4.0 signalr synchronous manualresetevent

SignalR无法使用返回值的客户端方法。所以我正在尝试创建一个帮助类来实现这一点。

所以这就是我想要做的事情:

  1. 服务器端:调用客户端方法并提供唯一的请求ID Client(clientId).GetValue(requestId)
  2. 服务器端:保存requestId并等待使用ManualResetEvent
  3. 的回答
  4. 客户端:内部void GetValue(Guid requestId)呼叫服务器方法hubProxy.Invoke("GetValueFinished", requestId, 10)
  5. 服务器端:通过requestId =>查找等待方法;设置返回值=>设置信号
  6. 服务器端:方法不再等待vor ManualResetEvent并返回检索到的值。
  7. 不幸的是,我能够让它工作。这是我的代码:

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

    使用这个MethodHandler类我需要有两个方法服务器端:

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

    客户端实现如下:

    // Method registration
    _hubProxy.On("GetValue", new Action<Guid>(GetValue));
    
    public void GetValue(Guid requestId)
    {
        int result = 10;
        _hubConnection.Invoke("GetValueResult", requestId, result);
    }
    

    问题:
    如果我呼叫服务器端GetValue("clientid")。不会调用客户端方法。如果我发表评论returnWaiter.Signal.WaitOne();,则会调用客户端GetValue并调用服务器端GetValueResult。但当然这次方法已经回归了。

    我认为与ManualResetEvent有关,但即使使用while(!returnWaiter.HasValue) Thread.Sleep(100);也无法解决此问题。

    如何解决此问题?

    提前致谢!

2 个答案:

答案 0 :(得分:3)

首先,我认为,如果您只是告诉我们您正在尝试做什么,而不是寻求如何使其同步的帮助,那么我们可以建议一个正确的方法来做到这一点。

你没有展示你的MethodHandler::Retrieve方法,但我几乎可以猜到它的样子,甚至不是真正的问题。我必须以最好的方式告诉你,这是真的坏主意。它根本就不会扩展。这只适用于单个SignalR服务器实例,因为您依赖于计算机专用资源(例如ManualResetEvent后面的内核对象)来提供阻止。也许你不需要扩展到一台服务器以满足你的要求,但即使在一台服务器上,这仍然是一种可怕的资源浪费。

您实际上是在正确的轨道上,客户端使用requestId作为关联标识符进行回叫。为什么不能使用该关联来恢复逻辑执行您在服务器端中间的任何进程?这样,在等待将消息传递到客户端,处理后,然后在示例中的后续消息GetValueResult中,不会有资源被发送回服务器实例。

答案 1 :(得分:0)

问题解决了:

问题仅发生在Hub.OnConnectedHub.OnDisconnected中。我没有确切的解释原因,但可能这些方法必须能够在它处理对客户端的方法调用之前完成。

所以我改变了代码:

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