I've written a .NET Core console application to monitor the operation of a sensor network and I'd like a review of the task scheduling performed by the main program loop. There are three tasks that are all declared as public static async Task
that perform the following operations:
CheckGateways.CheckAll
connects to an external API at one minute intervals and is the most likely to fail or take a long time. Normally it'll take a few seconds but I'd like other tasks to continue meanwhile.CheckNodes.CheckAll
is an internal database check that may take a few seconds but is not time critical and runs once per minute.CheckAlerts.CheckAll
checks for alert conditions on the sensors and is the most time critical so I'm checking that once per second.
While the code seems to be working it hasn't been well stress-tested and a few things I'd like reviewed are:
Are there any potential race conditions with the way I'm checking the status of the first two tasks?
I'm repeating the code to start / check the first two tasks so perhaps there's a cleaner way to do that without introduction too much extra code as there's unlikely to be any more tasks added.
Also any general comments on coding / naming standards would be appreciated.
static async Task Main() { IConfiguration configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .Build(); Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(configuration) .Enrich.FromLogContext() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .CreateLogger(); Task checkGateways = null; DateTime lastGatewayCheck = DateTime.UtcNow; Task checkNodes = null; DateTime lastNodeCheck = DateTime.UtcNow; while (true) { try { // Check gateway connectivity loss at one minute intervals if (DateTime.UtcNow.Subtract(lastGatewayCheck).TotalMinutes >= 1 && (checkGateways == null || checkGateways.IsCompleted)) { lastGatewayCheck = DateTime.UtcNow; checkGateways = CheckGateways.CheckAll(configuration); } if (checkGateways?.Status == TaskStatus.Faulted) { throw checkGateways.Exception; } // Check node connectivity loss at one minute intervals if (DateTime.UtcNow.Subtract(lastNodeCheck).TotalMinutes >= 1 && (checkNodes == null || checkNodes.IsCompleted)) { lastNodeCheck = DateTime.UtcNow; checkNodes = CheckNodes.CheckAll(configuration); } if (checkNodes?.Status == TaskStatus.Faulted) { throw checkNodes.Exception; } // Check for pending alerts to send at one second interval await CheckAlerts.CheckAll(configuration); } catch (AggregateException ae) { foreach (var ex in ae.InnerExceptions) { LogException(ex); } } catch (Exception ex) { LogException(ex); } await Task.Delay(1000); } } public static void LogException(Exception ex) { ConsoleMessage(ex.ToString()); Serilog.Log.Error(ex, "Error occured in TelemetryService"); } public static void ConsoleMessage(string msg) { Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss}: {msg}"); }