C# SignalR Core来自同一客户端的两个并发调用
1我有一个长任务(3mn),由Js客户端触发到Asp.Net核心信号器集线器 它工作得很好:C# SignalR Core来自同一客户端的两个并发调用,c#,asp.net-core,task,signalr-hub,asp.net-core-signalr,C#,Asp.net Core,Task,Signalr Hub,Asp.net Core Signalr,1我有一个长任务(3mn),由Js客户端触发到Asp.Net核心信号器集线器 它工作得很好: public class OptimizerHub : Hub, IOptimizerNotification { public async Task Optimize(LightingOptimizationInput lightingOptimizationInput) { LightingOptimizer lightingOptimizer = CreateLig
public class OptimizerHub : Hub, IOptimizerNotification
{
public async Task Optimize(LightingOptimizationInput lightingOptimizationInput)
{
LightingOptimizer lightingOptimizer = CreateLightingOptimizer();
Task t = lightingOptimizer.Optimize(lightingOptimizationInput);
await t;
}
}
2服务器回调客户端以通知进度、消息等
Clients.Caller.SendAsync(nameof(OnProgress), progress);
到目前为止效果还不错
3我希望通过对中心方法的客户端调用来取消任务
public Task Cancel()
{
GetContextLightingOptimizer()?.Cancel();
return Task.FromResult(0);
}
4问题
当客户机发出调用时,我在Chrome developer tools细节中看到它进入服务器。
在结束长任务结束(3mn)之前,调用无法到达服务器
5我尝试了许多解决方案
就像更改我的长任务调用方法,总是失败:
// Don't wait end of task, fails because the context disappear and can't call back the client :
// Exception : "Cannot access a disposed object"
public async Task Optimize(LightingOptimizationInput lightingOptimizationInput)
{
LightingOptimizer lightingOptimizer = CreateLightingOptimizer();
Task t = lightingOptimizer.Optimize(lightingOptimizationInput);
}
6种可能的解决方案
我现在想象的唯一解决方案是客户端在Http控制器中进行Http调用,传递连接id
这可能导致取消
该帖子提供了有关可能解决方案的信息:
7个问题
在处理第一个呼叫时,客户端是否有一种简单的方法对中心进行第二个呼叫
还有一篇关于并发调用的帖子:
我是否应该从上一篇文章推断,即使我的hub服务器方法可以多次调用客户端,它也无法处理来自客户端的任何其他调用?最后我找到了一个解决方案 它需要将信号器HubContext注入到自定义通知程序中 它允许:
public IOptimizerNotification Notification { get; set; }
public LightingOptimizer(IOptimizerNotification notification)
{
Notification = notification;
}
await Notification.OnProgress(0, 1000);
public class LightingOptimizer
{
private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private CancellationToken cancellationToken;
public LightingOptimizer( IOptimizerNotification notification )
{
Notification = notification;
cancellationToken = cancellationTokenSource.Token;
}
public void Cancel()
{
cancellationTokenSource.Cancel();
}
public async Task Optimize(LightingOptimizationInput lightingOptimizationInput)
{
for( int i+; i < TooMuchToBeShort ;i++)
{
if (cancellationToken.IsCancellationRequested)
throw new TaskCanceledException();
}
}
使依赖项注入要注入的HubContext
// that class can be in a business library, it is not SignalR aware
public interface IOptimizerNotification
{
string? ConnectionId { get; set; }
Task OnProgress(long currentMix, long totalMixes);
}
// that class has to be in the Asp.Net Core project to use IHubContext<T>
public class OptimizerNotification : IOptimizerNotification
{
private readonly IHubContext<OptimizerHub> hubcontext;
public string? ConnectionId { get; set; }
public OptimizerNotification(IHubContext<OptimizerHub> hubcontext)
{
this.hubcontext = hubcontext;
}
#region Callbacks towards client
public async Task OnProgress(long currentMix, long totalMixes)
{
int progress = (int)(currentMix * 1000 / (totalMixes - 1));
await hubcontext.Clients.Client(ConnectionId).SendAsync(nameof(OnProgress), progress);
}
#endregion
}
4来自工作对象的通知
public IOptimizerNotification Notification { get; set; }
public LightingOptimizer(IOptimizerNotification notification)
{
Notification = notification;
}
await Notification.OnProgress(0, 1000);
public class LightingOptimizer
{
private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private CancellationToken cancellationToken;
public LightingOptimizer( IOptimizerNotification notification )
{
Notification = notification;
cancellationToken = cancellationTokenSource.Token;
}
public void Cancel()
{
cancellationTokenSource.Cancel();
}
public async Task Optimize(LightingOptimizationInput lightingOptimizationInput)
{
for( int i+; i < TooMuchToBeShort ;i++)
{
if (cancellationToken.IsCancellationRequested)
throw new TaskCanceledException();
}
}
5开始业务对象长期工作
使用signal.ConnectionId注册业务对象(这里是LightingOptimizer)
以便以后可以检索业务对象
public class OptimizerHub : Hub
{
private static Dictionary<string, LightingOptimizer> lightingOptimizers = new Dictionary<string, LightingOptimizer>();
public async void Optimize(LightingOptimizationInput lightingOptimizationInput)
{
// the business object is created by DI so that everyting gets injected correctly, including IOptimizerNotification
LightingOptimizer lightingOptimizer;
IServiceScopeFactory factory = Context.GetHttpContext().RequestServices.GetService<IServiceScopeFactory>();
using (IServiceScope scope = factory.CreateScope())
{
IServiceProvider provider = scope.ServiceProvider;
lightingOptimizer = provider.GetRequiredService<LightingOptimizer>();
lightingOptimizer.Notification.ConnectionId = Context.ConnectionId;
// Register connectionId in Dictionary
lightingOptimizers[Context.ConnectionId] = lightingOptimizer;
}
// Call business worker, long process method here
await lightingOptimizer.Optimize(lightingOptimizationInput);
}
// ...
}
7对业务对象中的取消作出反应
public IOptimizerNotification Notification { get; set; }
public LightingOptimizer(IOptimizerNotification notification)
{
Notification = notification;
}
await Notification.OnProgress(0, 1000);
public class LightingOptimizer
{
private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private CancellationToken cancellationToken;
public LightingOptimizer( IOptimizerNotification notification )
{
Notification = notification;
cancellationToken = cancellationTokenSource.Token;
}
public void Cancel()
{
cancellationTokenSource.Cancel();
}
public async Task Optimize(LightingOptimizationInput lightingOptimizationInput)
{
for( int i+; i < TooMuchToBeShort ;i++)
{
if (cancellationToken.IsCancellationRequested)
throw new TaskCanceledException();
}
}
公共类照明优化器
{
private CancellationTokenSource CancellationTokenSource=新的CancellationTokenSource();
私有取消令牌取消令牌;
公共照明优化程序(IOPTimizer通知)
{
通知=通知;
cancellationToken=cancellationTokenSource.Token;
}
公开作废取消()
{
cancellationTokenSource.Cancel();
}
公共异步任务优化(LightingOptimizationInput LightingOptimizationInput)
{
for(int i+;i
可能会将长时间运行的任务移动到类似的位置,并使您的信号呼叫变短?您在服务器端的长时间运行操作是否提供了许多取消操作的好时机?您滥用了集线器。集线器应该是一个调停者,而不是一个主力,简言之,不要这样做。将呼叫转移到有状态且适合运行的位置通过消息或传递长时间运行的任务。在该环境中创建取消令牌,必要时使用字典并传回密钥,如果该令牌可用,则通过其密钥取消该令牌。实际上,有一个单独的类处理长时间调用。另一个类中需要HubContext来通知client@Caius是的,有一些好的时刻,因为我有一个包含许多迭代的循环,并且我已经在扫描CancellationToken了