【问题标题】:Connect with SingalR client with IHubContext provided in diffrent thread使用不同线程中提供的 IHubContext 与 SignalR 客户端连接
【发布时间】:2021-08-16 15:13:25
【问题描述】:

我正在使用 ASP.NET Core,并且我正在通过 SingalR 集线器端点将一些用户添加到集合中:

public class MatchMakingHub : Hub
{
    //....
    // called by client
    public async Task EnlistMatchMaking(int timeControlMs)
    {
        Guid currentId = Guid.Parse(this.Context.User.GetSubjectId());
        GetPlayerByIdQuery getPlayerByIdQuery = new GetPlayerByIdQuery(currentId);
        Player currentPlayer = await requestSender.Send<Player>(getPlayerByIdQuery);
        var waitingPlayer = new WaitingPlayer(currentPlayer, timeControlMs);
        this.matchMakePool.Add(waitingPlayer);
    }
}

matchMakePool 是一个单例集合。

稍后,我有一个 ASP.NET Core 后台服务从集合中获取用户,并通知他们被获取:

public class MatchMakingBackgroundService : BackgroundService
{
    private readonly MatchMakePoolSingleton matchMakePoolSingleton;
    private readonly IServiceProvider serviceProvider;
    private const int RefreshTimeMs = 1000;

    public MatchMakingBackgroundService(MatchMakePoolSingleton matchMakePoolSingleton, IServiceProvider serviceProvider)
    {
        this.matchMakePoolSingleton = matchMakePoolSingleton;
        this.serviceProvider = serviceProvider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while(!stoppingToken.IsCancellationRequested)
        {
            var result = matchMakePoolSingleton.RefreshMatches();
            var tasks = new List<Task>();

            foreach(var match in result)
            {
                tasks.Add(StartGameAsync(match));
            }

            await Task.WhenAll(tasks);
            await Task.Delay(RefreshTimeMs, stoppingToken);
        }
    }

    private async Task StartGameAsync(MatchMakeResult match)
    {
        using var scope = serviceProvider.CreateScope();

        var sender = scope.ServiceProvider.GetRequiredService<ISender>();

        var hubContext = serviceProvider.GetRequiredService<IHubContext<MatchMakingHub>>();

        CreateNewGameCommand newGameCommand = new CreateNewGameCommand(match.WhitePlayer.Id, match.BlackPlayer.Id, TimeSpan.FromMilliseconds(match.TimeControlMs));
        Guid gameGuid = await sender.Send(newGameCommand);
        
        await hubContext.Clients.User(match.WhitePlayer.Id.ToString()).SendAsync("NotifyGameFound", gameGuid);
        await hubContext.Clients.User(match.BlackPlayer.Id.ToString()).SendAsync("NotifyGameFound", gameGuid);
    }
}

我的问题是NotifyGameFound 没有在客户端被调用。当我直接从集线器本身通知他们时,它已收到,但由于某种原因,当我通过提供的IHubContext&lt;MatchMakingHub&gt; 调用它时它没有收到。我怀疑这是因为它在另一个线程上运行。

这是客户端代码:

// in blazor
protected override async Task OnInitializedAsync()
{
    var tokenResult = await TokenProvider.RequestAccessToken();

    if(tokenResult.TryGetToken(out var token))
    {
        hubConnection
        = new HubConnectionBuilder().WithUrl(NavigationManager.ToAbsoluteUri("/hubs/MatchMaker"), options =>
        {
            options.AccessTokenProvider = () => Task.FromResult(token.Value);
        }).Build();
        await hubConnection.StartAsync();

        hubConnection.On<Guid>("NotifyGameFound", id =>
        {
              //do stuff
        });
        await MatchMakeRequast();
    }
}

async Task MatchMakeRequast() =>
    await hubConnection.SendAsync("EnlistMatchMaking", Secs * 1000);

【问题讨论】:

  • 不确定它是否会有所帮助,但为什么要使用IServiceProvider 创建范围?有一个IServiceScopeFactory 就是为了这个。另外,我不明白您为什么要从您的范围之外获得IHubContext。我不确定没有它它会如何运作。
  • 您应该可以在注入服务的构造函数中使用IHubContext&lt;MatchMakingHub&gt; hubContext
  • 看起来您正在通过数据库中的用户 ID 选择 signalrClients,您确定在 ClaimTypes.NameIdentifier 声明中提供了正确的用户 ID 或提供了 IUserIdProvider 的正确实现?

标签: c# asp.net-core signalr blazor background-service


【解决方案1】:

我使用注入来实现这一点。

在我的服务器 Startup.cs ConfigureServices 我有:

services.AddScoped<INotificationsBroker, NotificationsBroker>();

在您的情况下,我假设您正在注入 MatchMakingBackgroundService 比如:

services.AddScoped<MatchMakingBackgroundService>();

在我的 NotificationsBroker 构造函数中,我注入了上下文:

private readonly IHubContext<NotificationsHub> hub;

public NotificationsBroker(IHubContext<NotificationsHub> hub)
    => this.hub = hub;

然后我将代理注入到我需要的任何服务中,该服务可以调用我通过接口公开的集线器方法。 你不需要额外的步骤,我这样做是为了测试,你可以将上下文直接注入你的MatchMakingBackgroundService

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多