2

My .NET5 Web API project (in addition to REST) listens to Telegram messages. The goals are simple:

  • when a user sends exit message - terminate the app with exit code 8
  • when a message handler throws a SeriousException - terminate the app with exit code 8

Unfortunately, due to the asynchronous nature of message polling, I'm struggling to achieve this goal. I've prepared a sample project in GitHub: https://github.com/chris-rutkowski/AsExPoC .

I found many similar subjects on Stack Overflow but nothing really works for me, moreover the plenty of suggested solutions (some AsyncHelpers etc) seems like overkill. I was thinking about passing Thread.CurrentThread to the service and somehow throw the exception on it, but I don't know how, or if it's event possible and even if it is, is it is a best practice. I don't have that much knowledge in C#+.NET, but In Swift (iOS), I would solve it so easily, just 3 lines including curly brackets:

  DispatchQueue.main.async {
      throw ForcedExitException()
  }

This is my stripped down .NET5 Web Api, you can find in the repo.

Program.cs

public class Program
    {
        public static int Main(string[] args)
        {
            try
            {
                var host = CreateHostBuilder(args).Build();
                var service = host.Services.GetRequiredService<DummyService>();
                service.Run();

                // other services are running too - I cannot just wait for DummyService to throw the exception

                host.Run();

                return 0;
            }
            catch (ForcedExitException)
            {
                return 8;
            }
            catch (Exception ex)
            {
                return 1;
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }

DummyService.cs

    public class DummyService
    {
        private readonly DummyMessenger _dummyMessenger;

        public DummyService()
        {
            _dummyMessenger = new DummyMessenger();
        }

        public async void Run()
        {
            _dummyMessenger.StartReceiving();
            _dummyMessenger.Add(async (_, message) => {
                Console.WriteLine($"received message {message}");
                await Task.Delay(50);

                if (message == "exit") 
                    throw new ForcedExitException();
            });
        }
    }

What I expect:

Exited with error code 8

This is what I see in the Console:

Exited with error code 134, with the following stacktrace
Unhandled exception. AsExPoC.ForcedExitException: Exception of type 'AsExPoC.ForcedExitException' was thrown.
at AsExPoC.DummyService.<>c.<Run>b__2_0(Object _, String message) in /Users/chris/Developer/Projects/AsExPoC/AsExPoC/DummyService.cs:line 36
at AsExPoC.DummyMessenger.StartReceiving() in /Users/chris/Developer/Projects/AsExPoC/AsExPoC/DummyMessenger.cs:line 27
at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__140_1(Object state)
at System.Threading.QueueUserWorkItemCallbackDefaultContext.Execute()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() 

Appreciate feedback.

2
  • 2
    I haven't given this too much thought, but have you tried Environment.Exit(Int32) Commented Jul 15, 2021 at 4:08
  • @TheGeneral thanks for showing interest. It doesn't change anything. I have created the sample project on GitHub: github.com/chris-rutkowski/AsExPoC . Meanwhile, I will update my question to be simpler. Commented Jul 15, 2021 at 5:57

1 Answer 1

3

I would start by moving your long running service into an IHostedService, forcing the host to stop if the service ever stops;

public class DummyService : BackgroundService
{
    private readonly IHostApplicationLifetime lifetime;

    public DummyService(IHostApplicationLifetime lifetime)
    {
        this.lifetime = lifetime;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            while (true)
            {
                //...
            }
        }
        catch (Exception)
        {
            Environment.ExitCode = 8;
        }
        lifetime.StopApplication();
    }
}

Then you can return Environment.ExitCode from your main method, or leave it as void return.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.