skip to Main Content

In the OnDisconnectedAsync event of my hub, I want to wait a few second before performing some action. I tried to make it async for using non-blocking 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);
    }

While this works as expected, I noticed that I can’t close the console application immediately. Seems that it waits for the 5 seconds delay to finish. How can I solve this, that exiting the application simply exite those delay, too?

The only alternative I see is Creating a classic thread and inject IHubContext, but this seems not scaling well and some kind of overkill for this simple task.

Backgrund

I have a list of online users. When users navigating through the multi page application, they get disconnected for a short time during the new HTTP request. To avoid such flickering in the online list (user get offline and directly online again), I want to remove the user on disconnect from the user list, but not notify the WS client immediatly.

Instead I want to wait 5 seconds. Only if the client is still missing in the list, I know the client hasn’t reconnected and I notify the other users. For this purpose, I need to sleep on the disconnect event. The above solution works well, except the delay on application exit (which is annoying during development).

A single page application like Angular or other frameworks shouldn’t be used for a few reasons, mainly performance and SEO.

2

Answers


  1. Chosen as BEST ANSWER

    I learned about the CancellationToken, which could be passed to Task.Wait. It could be used to abort the task. Creating such a token using CancellationTokenSource seems good to cancel the token programatically (e.g. on some condition).

    But I found the ApplicationStopping token in the IApplicationLifetime interface, that requests cancellation when the application is shutting down. So I could simply inject

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

    and only sleep if no cancellation is requested from this token

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

    This works because when closing the application, SignalR detects this as disconnect (altough it's caused from the server). So he waits 5000s before exit, like I assumed in my question. But with the token, IsCancellationRequested is set to true, so no additional waiting in this case.


  2. I would not have a problem with the program waiting the X seconds before close, because it is granted that no operation will stays in the middle of the operation.

    For example, what if the operation was doing something in the database? IT could leave the connection open.

    I would introduce a CancellationToken, and, instead of waiting 5 seconds, wait 1 or less and check again

    var cancellationToken = new CancellationToken(); // This in some singleton service
    

    And then

    var cont = 0;
    
    while (cont < 5 && !cancellationToken.IsCancellationRequested)
    {
        Task.Delay(1000);
        cont++;
    }
    
    if (cancellationToken.IsCancellationRequested)
    {
        return;
    }
    // Do something
    

    Then you can add IApplicationLifetime to let the app know when the signal to shit down comes and cancel your CancellationToken.

    You could even get this code in other class and generalize it to use it in other places.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search