繁体   English   中英

SignalR 核心来自同一客户端的两个并发调用

[英]SignalR Core two concurrent calls from same client

1 我有一个由 Js 客户端触发到 Asp.Net Core SignalR 集线器的长任务(3mn)

它工作正常:

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 我希望通过客户端调用 Hub 方法取消任务

public Task Cancel()
{
    GetContextLightingOptimizer()?.Cancel();
    return Task.FromResult(0);
}

4 问题

当客户端拨打电话时,我在 Chrome 开发人员工具详细信息中看到 go 到服务器。 在 end long 任务结束 (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 controller 中进行 Http 调用,并传递一个可以取消的连接 ID。

该帖子提供了有关可能解决方案的信息: Call SignalR Core Hub method from Controller

7个问题

是否有一种简单的方法可以让客户端在处理第一个呼叫时对集线器进行第二次呼叫?

还有一篇关于并发调用的帖子: SignalR multiple concurrent calls from client

我是否应该从上一篇文章中推断出,即使我的中心服务器方法可以多次调用客户端,它也无法处理来自客户端的任何其他调用?

最后我得到了解决方案

它需要在自定义通知程序中注入SignalR HubContext

它允许:

  1. 在长时间的工作中回调 Js 客户端
  2. 向客户提供某种报告(回调)
  3. 从客户端取消

以下是步骤

1 添加一个通知器object,其工作是回调Js客户端

使HubContext被 Dependency Injection 注入

// 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
}

2 在依赖注入系统中注册通知器object

在启动.cs

services.AddTransient<IOptimizerNotification, OptimizerNotification>();

3 获取通知器object注入worker object

public IOptimizerNotification Notification { get; set; }
public LightingOptimizer(IOptimizerNotification notification)
{
  Notification = notification;
}

4 工人通知 object

await Notification.OnProgress(0, 1000);

5 创业object长工

使用 SignalR.ConnectionId 注册业务 object(这里是 LightingOptimizer),以便以后可以检索业务 object

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

**6 在中心实施取消 **

从(当前)connectionId 中检索业务 object 并在其上调用 Cancel

public class OptimizerHub : Hub
{
    // ...
    public Task Cancel()
    {
      if (lightingOptimizers.TryGetValue(Context.ConnectionId, out LightingOptimizer? lightingOptimizer))
        lightingOptimizer.Cancel(); 
      return Task.FromResult(0);
    }
}

7 对业务取消的反应 object

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM