How should a service exit to prevent an error? - c#

I'm developing a Windows service in C#, .NET 6.0 following the instructions on https://learn.microsoft.com/en-us/dotnet/core/extensions/windows-service The service runs as expected, but when I stop the service, it logs an error
A task was canceled.
Exception:
System.Threading.Tasks.TaskCanceledException: A task was canceled.
at AWLService.WindowsBackgroundService.ExecuteAsync(CancellationToken stoppingToken) in C:\Minerva\Projects\AWLService_Test\WindowsBackgroundService.cs:line 22
The ExecuteAsync for this program is as follows:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
while (!stoppingToken.IsCancellationRequested)
{
string joke = _jokeService.GetJoke();
_logger.LogWarning("{Joke}", joke);
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "{Message}", ex.Message);
// Terminates this process and returns an exit code to the operating system.
// This is required to avoid the 'BackgroundServiceExceptionBehavior', which
// performs one of two scenarios:
// 1. When set to "Ignore": will do nothing at all, errors cause zombie services.
// 2. When set to "StopHost": will cleanly stop the host, and log errors.
//
// In order for the Windows Service Management system to leverage configured
// recovery options, we need to terminate the process with a non-zero exit code.
Environment.Exit(1);
}
}
I've tried catching TaskCanceledException. When I do that and add Environment.Exit(0), I still get the error. If I take out Environment.Exit, the service terminates without an error, but it logs "Service stopped successfully" twice.
What is the proper way I should be ending my service?
Thank you.
EDIT:
I added a handler for TaskCanceledException as follows:
catch (TaskCanceledException ex)
{
//Do nothing, we're exiting normally.
Environment.Exit(0);
}
When I do that, stopping the service through MMC shows an error "Error 1067: The process terminated unexpectedly" and a message is not logged indicating the service was stopped. Stopping it with sc on the command line doesn't show an error, but it also doesn't log a message that the service was stopped.
If I remove the Environment.Exit(0) line, MMC no longer shows an error, but the "Service stopped successfully" log message is written twice.

Catch the OperationCancelledException and log something different to the console in the handler. I suspect in your original handler (not shown) you were still probably doing this: _logger.LogError(ex, "{Message}", ex.Message);
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
while (!stoppingToken.IsCancellationRequested)
{
string joke = _jokeService.GetJoke();
_logger.LogWarning("{Joke}", joke);
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
catch (OperationCancelledException)
{
_logger.LogInformation("The service has stopped");
Environment.Exit(0);
}
catch (Exception ex)
{
_logger.LogError(ex, "{Message}", ex.Message);
}
}

Related

While true or while true with Thread.Sleep in a background service

I'm creating a background service that needs to run each x seconds. It has to be in .net Framework as the client does not want to upgrade to core or install anything on the machine other than this app. So I am restricted to using the Windows Service
My main issue is that I'm going in a while(true) loop that checks for the passed time (yes, I know I could use a timer) and I'm not sure if I should add a thread. Sleep in the loop or just leave the while(true). My main concern is not to overload the CPU/memory.
var nextIteration = DateTime.Now.Add(TimeSpan.FromSeconds(timer * (-1)));
while (true)
{
if (nextIteration < DateTime.Now)
{
RunService();
nextIteration = DateTime.Now.Add(TimeSpan.FromSeconds(timer));
}
}
If you are implementing a service of type BackgroundService you should consider using the CancellationToken in your while:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
//do work
await Task.Delay(TimeSpan.FromSeconds(x), stoppingToken);
}
catch (OperationCanceledException ex) when (cancellationToken.IsCancellationRequested)
{
//handle cancelation requested exception
}
catch (Exception ex)
{
//handle ex
}
}
}
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio

EventHub send message, but System.Net.Sockets.SocketException on Application Insights

I have this code below that sends message to azure EventHub, at first it works fine
public async Task SendMessage(string message) {
var producer = new EventHubProducerClient(this.connectionString, this.eventHubName);
using (EventDataBatch eventBatch = await producer.CreateBatchAsync()) {
if (!eventBatch.TryAdd(new EventData(message))) {
throw new Exception($"Event is too large for the batch and cannot be sent.");
}
try {
await producer.SendAsync(eventBatch);
}
catch(Exception ex) {
saveLog(message, ex);
}
finally {
await producer.DisposeAsync();
}
}
}
but in Application Insights I'm facing this message exception:
A Task's exception(s) were not observed either by Waiting on the Task
or accessing its Exception property. As a result, the unobserved
exception was rethrown by the finalizer thread. A connection attempt
failed because the connected party did not properly respond after a
period of time, or established connection failed because connected
host has failed to respond
Exception type:
System.Net.Sockets.SocketException
I've tried to add a ContinueWith (OnlyOnFaulted or OnlyOnCanceled), as below, to log it,
but now I get in the catch Exception (not in ExceptionHandle ) as "A task was canceled"
try {
await producer.SendAsync(eventBatch)
.ContinueWith(t => ExceptionHandle(t, message), TaskContinuationOptions.OnlyOnFaulted);
}
How can I handle these exceptions that App Insights display?
If you need to handle the task exception by using a ContinueWith (OnlyOnFaulted or OnlyOnCanceled) we can get this exception in catch block only. By default, if the task runs successfully, it throws Exception if the Task IsFaulted we can get the exception from Catch Block.
To fix this issue:
Use UnobservedTaskException event which observe the Task Exceptions and it doesn't terminate by default. The exception can be handled by the runtime.

Logging exceptions in fire&forget tasks

I try to start some action in background, I am not interested in its result, but in case of an error I'd like to log it, and - of course - prevent the application (here: a Windows service) from crashing.
public static void CreateAndStartTaskWithErrorLogging(Action _action, string _componentName, string _originalStacktrace = null)
{
DateTime started = HighPrecisionClock.Now;
Task task = new Task(_action);
task.ContinueWith(_continuation => _continuation.LogExceptions(_componentName, started, _originalStacktrace));
task.ConfigureAwait(false);
task.Start();
}
internal static void LogExceptions(this Task _t, string _componentName, DateTime _started, string _originalStacktrace = null)
{
try
{
_t.Wait(1000);
}
catch (Exception ex)
{
Logger.LogError(_componentName, $"An exception occurred in a fire-and-forget task which was started at {_started}.\r\n" +
$"The original stack trace is:\r\n{_originalStacktrace}");
Logger.LogException(_componentName, ex);
}
try
{
_t.Dispose();
}
catch (Exception dex)
{
Logger.LogException(_componentName, dex);
}
}
Without ConfigureAwait(false) and without _t.Dispose(), the catch works and logs the exception. But the application crashes several seconds later (i.e. on the Finalizer thread?). The entry in the Microsoft Event Viewer shows that exception.
With ConfigureAwait and _t.Dispose(), I do not see the exception in the logs, the application just crashes.
What's wrong with the idea shown above?
Edit:
Meanwhile I tested without ConfigureAwait but with _t.Dispose. I could catch about 10 such exceptions, and none made the application crash. That seems to solve the issue, but I do not understand the reason for that, so the situation is still bad.
What does ConfigureAwait(false) do to Exceptions in the task (or in tasks started within that task, e.g. by a Parallel.ForEach further down)?
Why does the Dispose - which is called on the continuation, not the task proper according to a comment - prevent the crash (the Finalizer does not call Dispose, but Dispose may set some flags influencing its behavior)?
Edit 2:
Also that does not work all the time, only most of the time. Suggested solution 1 below also fails sometimes.
In the crashing context, the function is called with Utilities.TaskExtensions.CreateAndStartTaskWithErrorLogging(() => DataStore.StoreSyncedData(data), Name);, where DataStore is set to a composite which in turn calls Parallel.ForEach(m_InnerDataStores, _store => { _store.StoreSyncedData(_syncedData); }); on its members. One of them writes a video with the Accord library, which sometimes causes an AccessViolation at <Module>.avcodec_encode_video2(libffmpeg.AVCodecContext*, libffmpeg.AVPacket*, libffmpeg.AVFrame*, Int32*), i.e. the exception may come from non-managed code.
Of course, I could try to catch it somewhere down there - but that's not the objective of this method. I expect it to be able to safely run any code in the background without crashing the application.
This is my suggestion for logging errors:
public static void OnExceptionLogError(this Task task, string message)
{
task.ContinueWith(t =>
{
// Log t.Exception
}, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
}
Usage example:
var task = Task.Run(action);
task.OnExceptionLogError("Oops!");
try
{
await task;
}
catch
{
// No need to log exception here
}

Trouble catching exception on Azure Service Bus SendAsync method

I'm trying to use the following code to setup a failure condition, namely were there is no network path available, so the code shouldn't be able to send to the Service bus at all. I know this because I disable my network ports when I test.
I am still having trouble with the Async nature of the code though. I don't know in a console application like I have how to attach something that would log out the exception that I know should be generated.
How do I see that exception text?
public async Task TestQueueExists()
{
_queueClient = new QueueClient(AppSettings.McasServiceBusConnectionString,
AppSettings.ListServSyncQueueName);
Logger.Information(
$"Queue Created to: {_queueClient.QueueName} with RecieveMode: {_queueClient.ReceiveMode}");
try
{
await _queueClient.SendAsync(new Message("Test".ToUtf8Bytes()));
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
According to your code, I assumed that you are using the Azure Service Bus .NET Standard client library Microsoft.Azure.ServiceBus. Per my test, you could leverage the following code to capture the exception as follows:
try
{
await _queueClient
.SendAsync(new Message(Encoding.UTF8.GetBytes("hello world")))
.ContinueWith(t =>
{
Console.WriteLine(t.Status + "," + t.IsFaulted + "," + t.Exception.InnerException);
}, TaskContinuationOptions.OnlyOnFaulted);
Console.WriteLine("Done");
}
catch (Exception e)
{
Console.WriteLine(e);
}
If the network is break, you may capture the exception as follows:
The issue I ran into was that I was using a Console application and the Console app runs sync out of the box. I had to modify my Main to return a Task and use async for the exception to be caught, which makes sense.
ie
internal class Program
{
static async Task Main(string[] args)
{
[..]
}
}

Proper exception handling when invoking WCF callback method

I have a series of WCF services that invoke methods on the client again (using WcfDuplexChannels) based on events at the server side. However, it seems there are quite some exceptions that can occur, so right now I have a huge try/catch block around every line calling back to the client, ending with disabling of the event in case any exception occurs. Besides being cumbersome to write every time, I'm not sure if I could simplify the try catch block by catching just a few base exceptions? Right now I don't really care what's causing the exceptions (I don't care whether it's faulted, aborted, disposed or timed out) but I do log the different exceptions.
I also read about IErrorHandler, but will that actually be suitable when invoking a method on the client?
Here's a sample of my current strategy:
private void OnProductChanged(List<DTO> products)
{
try
{
client.OnProductChanged(products);
return;
}
catch (TimeoutException)
{
log.Info("Communication to client timed out.");
}
catch (CommunicationObjectAbortedException)
{
log.Info("Connection to client is in aborted state.");
}
catch (CommunicationObjectFaultedException)
{
log.Info("Connection to client is in faulted state.");
}
catch (CommunicationException ce)
{
log.InfoFormat("CommunicationException occured on product change notification: {0}.", ce.Message);
}
catch (ObjectDisposedException)
{
log.Info("Communication channel is disposed.");
}
catch (Exception e)
{
log.WarnFormat("Unhandled {0} on client callback: {1}", e.GetType(), e.Message);
}
SendProductChanged = false;
}
The SendProductChanged = false; line will take care of unbinding the event handler.
You can write a wrapper method which takes Actions of Funcs as parameters and you can use try catch blocks inside this function. You can call your functions using this function; something like:
public void CallMethod(Action methodToBeCalled)
{
try
{
methodToBeCalled();
}
catch
.....
....
}
Then call your functions like:
CallMethod(() => client.OnProductChanged(products));

Categories