Code review #3 – use threads like Mr Fowler

This is another post in a series about great code review feedback, that I either gave or received. It will always consist of 3 parts: context, review feedback and explanation. You can go ahead and read the previous ones here: https://www.michalbialecki.com/2019/06/21/code-reviews/. This post is about doing things the right way.

The context

In my daily job we have a custom scheduling service, that is very simple. It just makes POST requests without a body based on a CRON expression. Then it check if the request was successful. The problem occured when jobs were getting longer and longer so that they reached a request timeout and apperad to be failed, while on a receiver, they run just fine. We decided, that a receiver should just return a success and do it’s work in a separate thread. This is how I approached this task:

Instead of having call with an await:

[HttpPost("ExportUsers")]
public async Task<IActionResult> ExportUsers()
{
    var result = await _userService.ExportUsersToExternalSystem();
    return result ? Ok() : StatusCode(StatusCodes.Status500InternalServerError);
}

I added a simple wrapper with Task.Run

[HttpPost("ExportUsers")]
public IActionResult ExportUsers()
{
    Task.Run(
        async () =>
            {
                var result = await _userService.ExportUsersToExternalSystem();
                if (!result)
                {
                    // log error
                }
            });
            
    return Ok();
}

Review feedback

Start a new thread as David Fowler does.

Explanation

When using Task.Run a thread is blocked from a thread pool. This is not a bad idea, but it should be used wisely. The thread should be released in a reasonable timeframe, not blocked for a lifetime of an application. An anti-pattern here would be to start listening Service Bus messages inside Task.Run, which would block a thread forever.

After refactoring my code looks like this:

[HttpPost("ExportUsers")]
public IActionResult ExportUsers()
{
    var thread = new Thread(async () =>
    {
        var result = await _userService.ExportUsersToExternalSystem();
        if (!result)
        {
            // log error
        }
    })
    {
        IsBackground = true
    };
    thread.Start();

    return Ok();
}

You can look at the more detailed explanation by David Fowler here: https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/HEAD@%7B2019-05-16T19:27:54Z%7D/AsyncGuidance.md#avoid-using-taskrun-for-long-running-work-that-blocks-the-thread

There are many examples here that really makes you think about how to write better code, so I strongly encourage you to read it. Enjoy! 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *