简体   繁体   中英

No exception logged in async task

I found a lot of similar posts, but none outlining the problem i had specifically.

I have a C# Worker project. In the StartAsync method of the worker, i call await TryConnect(); TryConnect looks like this:

private async Task TryConnect()
{
        Logging.Log("1");
        var startResult = await StartUserStreamAsync();

        Logging.Log("2");
        if (!startResult.Success)
        {
            Logging.Log("3");
            throw new Exception($"Failed to start: {startResult.Error}");
        }
        listenKey = startResult.Data;
        Logging.Log("4");
}

Calling site looks like this:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
        //initialize
        await Initialize();            
        ///other code
}

private async Task Initialize()
{
        ///other code
        await TryConnect();
        ///other code
}

As you can probably guess. 1, 2 and 3, are logged in console. However, 4 isn't, and the exception is thrown.

In previous normal fully sync console applications. This just throws an uncaught exception, the application stops execution, and all is as i expect it to be. I do not intend to recover from that state. However in async land, it stays silent. No exception is thrown, nothing to indicate anything is wrong except execution of the calling method seems to halt.

Why is this, and what should i be doing? Am i forced to try/catch it, and if so, what if you forget? Are you just left to figure out your not so obvious mistake?

I have blog posts describing both the silent failure and application lifetime issues. In summary, even though both behaviors are surprising, they are both by design.

In my own code, I use a CriticalBackgroundServiceBase type that derives from BackgroundService , and ensures exceptions are logged (rather than ignored) and that when the service ends the application ends.

The async aspect of the code you posted is correct and works as expected.

However, since you are working with a Worker, assumptions from other application types do not hold.

As the worker is implemented through a BackgroundService , the ExecuteAsync method only causes failures when it throws synchronously. This is interpreted as an error during service start-up.

Once ExecuteAsync suspends (first await that does actual asynchronous work is encountered), the BackgroundService assumes that normal service work is now in progress and that it is started successfully. After that, the service is assumed to be running.

Once the service is shut down (eg by being stopped in the service manager, sudo systemctl stop etc.), it will trigger the cancellation token passed to ExecuteAsync and await the returned Task . At this point, it would encounter the exception you expect.

My understanding is that services should not require user interaction, they should just keep doing whatever they do, being resilient to any possible errors in their process, reporting any errors to corresponding OS facilities (eg EventLog). As such, any code that is done in ExecuteAsync should be wrapped in appropriate try/catch, possibly except the OperationCanceledException thrown from the cancellation token on shutdown.

If, at some point, you realize the service can no longer function, you can terminate the hosting process through IHostApplicationLifetime.StopApplication ( doc )

If you require asynchronous work during start-up, it is possible to implement your worker as IHostedService directly and implement the StartAsync method as you see fit.

See also BackgroundService source on GitHub for reference. Microsoft Docs also has Implement the IHostedService interface tutorial.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM