简体   繁体   English

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

[英]SignalR Core two concurrent calls from same client

1 I have a long task (3mn) triggered by a Js client to an Asp.Net Core SignalR Hub 1 我有一个由 Js 客户端触发到 Asp.Net Core SignalR 集线器的长任务(3mn)

It works fine:它工作正常:

public class OptimizerHub : Hub, IOptimizerNotification
{
    public async Task Optimize(LightingOptimizationInput lightingOptimizationInput)
    {
        LightingOptimizer lightingOptimizer = CreateLightingOptimizer();
        Task t = lightingOptimizer.Optimize(lightingOptimizationInput);
        await t;
    }
}

2 The server callbacks the client to notify progress, messages, ... 2 服务器回调客户端通知进度,消息,...

Clients.Caller.SendAsync(nameof(OnProgress), progress);

It works fine so far.到目前为止它工作正常。

3 I want the task to be cancellable with a client call to a Hub method 3 我希望通过客户端调用 Hub 方法取消任务

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

4 The problem 4 问题

When the client makes the call, I see it go to the server in Chrome developer tools detail.当客户端拨打电话时,我在 Chrome 开发人员工具详细信息中看到 go 到服务器。 The call doesn't get to the server before the end long task ends (3mn) !在 end long 任务结束 (3mn) 之前调用没有到达服务器!

5 I have tried many solutions 5 我尝试了很多解决方案

Like changing my long task call method, always failing:就像更改我的长任务调用方法一样,总是失败:

    // 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 Possible solutions 6 可能的解决方案

The only solution I imagine right now is the client making an Http call in a Http controller, passing a connection id that could make the cancellation.我现在想的唯一解决方案是客户端在 Http controller 中进行 Http 调用,并传递一个可以取消的连接 ID。

That post provides information about a possible solution: Call SignalR Core Hub method from Controller该帖子提供了有关可能解决方案的信息: Call SignalR Core Hub method from Controller

7 Questions 7个问题

Is there a simple way that the client makes a second call to the hub while a first call is being processed?是否有一种简单的方法可以让客户端在处理第一个呼叫时对集线器进行第二次呼叫?

There is also a post giving about concurrent calls: SignalR multiple concurrent calls from client还有一篇关于并发调用的帖子: SignalR multiple concurrent calls from client

Should I deduce from the previous post that even if my hub server method can make calls many times to the client, it can't process any other call from the client?我是否应该从上一篇文章中推断出,即使我的中心服务器方法可以多次调用客户端,它也无法处理来自客户端的任何其他调用?

At last I got a solution最后我得到了解决方案

It required to have the SignalR HubContext injected in a custom notifier它需要在自定义通知程序中注入SignalR HubContext

It allows:它允许:

  1. to callback the Js client during the long work在长时间的工作中回调 Js 客户端
  2. to have som kind of reports (callback) to client向客户提供某种报告(回调)
  3. to cancel from the client side从客户端取消

Here are the steps以下是步骤

1 Add a notifier object whose job is to callback the Js client 1 添加一个通知器object,其工作是回调Js客户端

Make the HubContext to be injected by the Dependency Injection使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 Register the notifier object in the Dependency Injection system 2 在依赖注入系统中注册通知器object

In startup.cs在启动.cs

services.AddTransient<IOptimizerNotification, OptimizerNotification>();

3 Get the notifier object to be injected in the worker object 3 获取通知器object注入worker object

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

4 Notify from the worker object 4 工人通知 object

await Notification.OnProgress(0, 1000);

5 Start Business object long work 5 创业object长工

Register business object (here it's LightingOptimizer) with a SignalR.ConnectionId so that business object can be retrived later使用 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 Implement Cancellation in the hub ** **6 在中心实施取消 **

Retrieve business object from (current) connectionId and call Cancel on it从(当前)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 React to Cancellation in Business object 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