Caius Jard
Caius Jard

Reputation: 74605

Simple way to fire-and-forget SignalR calls in netcore 3.1

I've a web service in .net core 3.1 that makes reasonably heavy use of SignalR. The original code just calls methods that call SignalR SendAsync methods, and though there's perhaps a bit of unnecessary task wrapping, ultimately nothing is done with any Task that is related to a SignalR call:

    //in the hub manager
    public async Task ShowBanner(string group)
    {
        await _signalrHubContext.Clients.Group(group).SendAsync("showBanner");
    }

    //in the controller
    [HttpGet("Show/Banner")]
    public async Task<ActionResult> ShowBanner()
    {
        try {
            //some other await-necessary db stuff here
            await dbcontext.Blah....;

            somehubmanager.ShowBanner(); //because this call is not awaited...

            return Ok();
        }
        catch (Exception e)
        {
            return StatusCode((int)HttpStatusCode.InternalServerError, e.Message);
        }
    }

Which naturally results in a lot of "because this call is not awaited...". I'd like to modify this scenario so it's "deliberately" fire and forget (we really don't care whether a client receives/obeys the instruction to show its banner or not, we don't want to do anything with errors, we don't want to wait for any completions, nor do we care if there are waiting tasks if the server is shut down - we only really care that the messages are tried if possible) and remove the hundreds of warnings for these particular uses

Would implementing the background work queue functonality detailed on MSDN be the way to go for this? Or am I overthinking it, and I should just discard the Task? The docs for SendAsync say "doesn't wait for..", and things work acceptably right now but I'm also curious if this is just purely coincidentally because the SignalR stuff is quicker/more light weight than the time required to finish up the request to the service; could we hit a go-slow one day such that we find that that web service request finishes and wraps up before anything happens with the SignalR messaging and the messages wouldn't even be tried (i.e. could the scoped hubmanager be disposed by the DI before ShowBanner is called)

I'm just not sure how long I have to "take care" of the Task in the specific context of SignalR; would the underlying messaging still work the same if I changed things thus:

    public Task ShowBanner(string group)
    {
        return _signalrHubContext.Clients.Group(group).SendAsync("showBanner");
    }

    [HttpGet("Show/Banner")]
    public async Task<ActionResult> ShowBanner()
    {
        try {
            //some other await-necessary db stuff here
            await dbcontext.Blah....;

            _ = somehubmanager.ShowBanner();

            return Ok();
        }
        catch (Exception e)
        {
            return StatusCode((int)HttpStatusCode.InternalServerError, e.Message);
        }
    }

Upvotes: 0

Views: 861

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 456437

Would implementing the background work queue functonality detailed on MSDN be the way to go for this? Or am I overthinking it, and I should just discard the Task?

A background queue would give you a "best effort" at running the code. The background queue gives you a way to interact with the hosting process and will delay shutdown if possible to run the code. That said, if it's just not that important, discarding the task is easier.

would the underlying messaging still work the same if I changed things thus

Yes. MethodAsync(); is the same as _ = MethodAsync();. Both lines will call the method and then ignore the returned task. The only difference is the explicit discard (_), which is essentially you telling the compiler "yes, I know this task isn't awaited, and I'm doing it on purpose".

Upvotes: 4

Related Questions