Asp.net Core used SignalR ObjectDisposedException solution appears

SignalR appear ObjectDisposedException solution

Problem Description

Functional requirements

Business to implement server-side push some regular updates to the client, the idea of ​​the program is: When a client connects up to record its ConnectionId, use Timer to create a timed task, timed to ConnectionId client sends a message.

Abnormal position

Get out ObjectDisposedException client connection Clients.Client (connId) or other methods in the timing task.
Abnormal Picture
Abnormal position code:

public async void LoopTask(object state)
{
	// state是ConnectionId
    string connId = state.ToString();

    // 任何方式获取客户端连接都会冒ObjectDisposedException
    var client = Clients.Client(connId);

    string arg = "this is an argument";
    await client.SendAsync("Test", arg);
}

The code in question overview:

    public class TestHub : Hub
    {
        // 定时任务字典。Dictionary不是线程安全的,实际应用中应该加锁或者使用ConcurrentDictionary
        private static Dictionary<string, Timer> Tasks { get; set; }

        static TestHub()
        {
            Tasks = new Dictionary<string, Timer>();
        }

        public override Task OnDisconnectedAsync(Exception exception)
        {
            string connId = Context.ConnectionId;
            if(Tasks.TryGetValue(connId,out Timer timer))
            {
                timer.Dispose();
            }
            return base.OnDisconnectedAsync(exception);
        }

        public override Task OnConnectedAsync()
        {
            string connId = Context.ConnectionId;
            // 0毫秒后开始每5秒执行一次
            Timer timer = new Timer(LoopTask, connId, 0, 5000);
            Tasks.Add(connId, timer);
            return base.OnConnectedAsync();
        }

        public async void LoopTask(object state)
        {
            string connId = state.ToString();
            // 任何方式获取客户端连接都会冒ObjectDisposedException
            var client = Clients.Client(connId);
            string arg = "this is an argument";
            await client.SendAsync("Test", arg);
        }
    }

Solution

Because the Hub is temporary, it will dispose off request is completed, the operation in all requests can not be used directly inside the object Hub (Clients, Groups etc.) acquired client connections.
Introduction of the document from Microsoft
Here alone to open a client connection class, using Asp.net core comes IHubContext dependency injection is injected into the class, the class with the operation IHubContext.
It should be noted here that, Asp.net and Asp.net core is not the same, Asp.net can get in with GlobalHost to IHubContext, while in Asp.net core can not be obtained in this manner.
Introduction of the document from Microsoft
After modifying the code overview:

    public class ClientConnection : IDisposable
    {
    	// 连接标识,也可以是userId等,反正能定位到客户端就行
        public string ConnectionId { get; set; }
		
		// HubContext,用于获取客户端连接
        private IHubContext<TestHub> Context { get; set; }

		// 定时任务
        private Timer Timer { get; set; }

        public ClientConnection(IHubContext<TestHub> hubContext, string connId)
        {
            this.ConnectionId = connId;
            this.Context = hubContext;

            // 0毫秒后开始每5秒执行一次
            Timer = new Timer(LoopTask, null, 0, 5000);
        }

        public async void LoopTask(object state)
        {
            var client = Context.Clients.Client(this.ConnectionId);
            string arg = "this is an argument";

            await client.SendAsync("Test", arg);
        }

        public void Dispose()
        {
            this.Timer.Dispose();
        }
    }

    public class TestHub : Hub
    {
        // 用于获取依赖注入对象
        private IServiceProvider Service { get; set; }

        // 这里其实不应该把连接保存在Hub中,demo的话随便了(记得加锁)
        private static List<ClientConnection> Connections;

        static TestHub()
        {
            Connections = new List<ClientConnection>();
        }

        public TestHub(IServiceProvider service)
        {
            Service = service;
        }

        public override Task OnDisconnectedAsync(Exception exception)
        {
            string connId = Context.ConnectionId;

            var conn = Connections.Find(s => s.ConnectionId == connId);
            if(conn != null)
            {
                Connections.Remove(conn);
                conn.Dispose();
            }

            return base.OnDisconnectedAsync(exception);
        }

        public override Task OnConnectedAsync()
        {
            string connId = Context.ConnectionId;
			// 这里不用在StartUp里面AddScoped也可以获取到
            IHubContext<TestHub> context = Service.GetService(typeof(IHubContext<TestHub>)) as IHubContext<TestHub>;
            ClientConnection conn = new ClientConnection(context, connId);
            Connections.Add(conn);

            return base.OnConnectedAsync();
        }
    }

problem causes

Cause of the problem is that the temporary Hub instance, like Controller, a request to generate an instance, seems plausible. However, although the Hub is temporary but it manages connections are persistent (WebSocket connection length), which is very confusing, easy to think of the Hub is persistent.
Here Insert Picture Description
Finally, the proposed process into the pit or to see more of the document, in fact, Microsoft's documentation really done a good job, although many of them are machine translation, and the words are more obscure, but the focus will be specifically stated, this can give praise.

Reference
Asp.net Core documents in SignalR get IHubContext

Published 15 original articles · won praise 2 · Views 4852

Guess you like

Origin blog.csdn.net/weixin_38138153/article/details/103228916