SignalR connection wouldn't start inside a sin

2019-09-06 09:38发布

问题:

I have setup my signalR connection inside a singleton class so I can use the same connection throughout my entire project. The problem is that the connection never starts and the code never executes beyond the line await hubConnection.Start() however when I do this outside the single class, then the connection is initiated in an instant. I wonder what I'm doing wrong. Here's my definition of the singleton class:

public sealed class ProxySubscriber
{
    private static volatile ProxySubscriber instance;
    private static object syncRoot = new Object();
    private IHubProxy chatHubProxy = null;
    private HubConnection hubConnection = null;
    public event Action<string, string> OnConnect;
    private ProxySubscriber()
    {
        if (hubConnection == null) { hubConnection = new HubConnection("http://cforbeginners.com:901"); }
        if (chatHubProxy == null) { chatHubProxy = hubConnection.CreateHubProxy("ChatHub"); }

        chatHubProxy.On<string, string>("onConnected", (id, username) => OnConnect.Invoke(id, username));
    }

    private async Task<string> StartConnection()
    {
        await hubConnection.Start();

        return "Connection started..";
    }

    public async void InvokeConnect()
    {
        await chatHubProxy.Invoke("Connect", "Mujtaba");
    }
    public static async Task<ProxySubscriber> GetInstance()
    {
        if (instance == null)
        {
            lock (syncRoot)
            {
                if (instance == null)
                {
                    instance = new ProxySubscriber();

                }
            }
        }

        await instance.StartConnection();
        return instance;
    }
}

I'm using the singleton class like this:

ProxySubscriber proxySubscriber = ProxySubscriber.GetInstance().Result;
proxySubscriber.OnConnect += proxySubscriber_OnConnect;
proxySubscriber.InvokeConnect();

回答1:

First thing, don´t make your GetInstance() async. Use it just to create an instance with no more logic in it.

Make StartConnection public and call it from outside, awaiting the call:

await ProxySubscriber.GetInstance().StartConnection();
ProxySubscriber.GetInstance().OnConnect += proxySubscriber_OnConnect;
ProxySubscriber.GetInstance().InvokeConnect();

If you need to run this code from non async methods:

Task.Factory.StartNew(async () => {
    await ProxySubscriber.GetInstance().StartConnection();
    ProxySubscriber.GetInstance().OnConnect += proxySubscriber_OnConnect;
    ProxySubscriber.GetInstance().InvokeConnect();
});

More details:

  1. The name of your class doesn´t look correct to me. It would be better something like SignalRClientHelper.
  2. You don´t need to check for null values on the constructor, as it will be null with no doubt if (hubConnection == null)
  3. Why do you have a "Connect" method on the server? When you do await hubConnection.Start(); you are already connecting. If you just want to asign a username to a connection, you can pass that username in a header before you call Start(): hubConnection.Headers.Add("username", "Mujtaba");
  4. If you want to make this class reliable, you need to handle SignalR disconnections (but that would be a different question)
  5. Whenever you call chatHubProxy.Invoke("whatever"); you may get an exception if the connection is lost at some point, and that will make your application crash. Wrap it in a try/catch statement and handle the exception as you need.
  6. StartConnection should check if there is already a connection going on. So if you call it 100 times, it would connect just once.

In general, you should learn from SignalR connection live cycle and implement some logic at the time the connection is lost.

Take a look here, here and here