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 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.
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.
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.
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.