簡體   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