首页 > 解决方案 > 在 SignalR Core 断开事件中等待几秒钟而不阻塞应用程序存在

问题描述

如果OnDisconnectedAsync是我的集线器,我想在执行某些操作之前等待几秒钟。我试图使其异步以使用非阻塞Task.Delay

    public override async Task OnDisconnectedAsync(Exception exception) {
        var session = (VBLightSession)Context.Items["Session"];
        activeUsers.Remove(session.User.Id);

        await Task.Delay(5000);
        if(!activeUsers.Any(u => u.Key == session.User.Id)) {
            await Clients.All.SendAsync("UserOffline", UserOnlineStateDto(session));
        }

        await base.OnDisconnectedAsync(exception);
    }

虽然这按预期工作,但我注意到我无法立即关闭控制台应用程序。似乎它等待 5 秒延迟完成。我该如何解决这个问题,退出应用程序也只是退出这些延迟?

我看到的唯一选择是创建一个经典线程并注入IHubContext,但这似乎不能很好地扩展,并且对于这个简单的任务来说有点矫枉过正。

背景

我有一个在线用户列表。当用户在多页应用程序中导航时,他们在新的 HTTP 请求期间会在短时间内断开连接。为了避免在线列表中出现这种闪烁(用户下线并再次直接在线),我想从用户列表中删除断开连接的用户,但不立即通知 WS 客户端。

相反,我想等待 5 秒钟。仅当列表中仍然缺少客户端时,我才知道客户端尚未重新连接并通知其他用户。为此,我需要在断开连接事件上睡觉。上述解决方案效果很好,除了应用程序退出延迟(这在开发过程中很烦人)。

不应该使用像 Angular 或其他框架这样的单页应用程序,主要是性能和 SEO。

标签: c#asp.net-coresignalrasp.net-core-2.1

解决方案


我了解了CancellationToken,它可以传递给Task.Wait. 它可以用来中止任务。使用创建这样的令牌CancellationTokenSource似乎可以很好地以编程方式取消令牌(例如在某些情况下)。

但是我在界面中找到了 ApplicationStopping 令牌IApplicationLifetime,它在应用程序关闭时请求取消。所以我可以简单地注入

namespace MyApp.Hubs {
    public class MyHub : Hub {
        readonly IApplicationLifetime appLifetime;
        static Dictionary<int, VBLightSession> activeUsers = new Dictionary<int, VBLightSession>();
        public MyHub(IApplicationLifetime appLifetime) {
            this.appLifetime = appLifetime;
        }
    }
}

并且仅在没有从此令牌请求取消时才休眠

public override async Task OnDisconnectedAsync(Exception exception) {
    var session = (VBLightSession)Context.Items["Session"];
    activeUsers.Remove(session.User.Id);
    // Prevents our application waiting to the delay if it's closed (especially during development this avoids additionally waiting time, since the clients disconnects there)
    if (!appLifetime.ApplicationStopping.IsCancellationRequested) {
        // Avoids flickering when the user switches to another page, that would cause a directly re-connect after he has disconnected. If he's still away after 5s, he closed the tab
        await Task.Delay(5000);
        if (!activeUsers.Any(u => u.Key == session.User.Id)) {
            await Clients.All.SendAsync("UserOffline", UserOnlineStateDto(session));
        }
    }
    await base.OnDisconnectedAsync(exception);
}

这是有效的,因为在关闭应用程序时,SignalR 将其检测为断开连接(尽管它是由服务器引起的)。所以他在退出前等待了 5000 秒,就像我在问题中假设的那样。但是有了token,IsCancellationRequested设置为true,所以在这种情况下没有额外的等待。


推荐阅读