I have a console application (dotnet core, ubuntu), that looks like following:

    void Main()
    {
        try
        {
            var job = new Job();
            job.Start();

            while(Console.ReadLine() != "quit");
        }
        catch(Exception e)
        {
            //some handling
        }
    }

While `Job` is implementation of `JobAbstract` class:

    public class JobAbstract
    {
        private readonly int _periodMs;

        protected JobAbstract(int periodMs)
        {
            _periodMs = periodMs;
        }

        public bool Working { get; private set; }

        public abstract Task Execute();

        private void LogFatalError(Exception exception)
        {
            try
            {
                //logging
            }
            catch (Exception)
            {
            }
        }

        private async Task ThreadMethod()
        {
            while (Working)
            {
                try
                {
                    await Execute();
                }
                catch (Exception exception)
                {
                    LogFatalError(exception);
                }
                await Task.Delay(_periodMs);
            }
        }

        public virtual void Start()
        {
            if (Working)
                return;
            Working = true;
            Task.Run(async () => { await ThreadMethod(); });
        }

        public void Stop()
        {
            Working = false;
        }
    }

`Job` is defined like:

    public class Job : JobAbstract
    {
        public Job(periodMs) : base(periodMs)
        {}
        
        public override async Task Execute()
        {
            await SomeTask();
            OtherKindOfJob();
            await MaybeMoreAsyncTasks();
            // etc
        }
    }

It all works fine, as you might expect.

Now, I'm wrapping it all up in docker containers for continuous delivery. `docker stop` might be run on a container while `Execute` method of `Job` is run. In order to wait for the cycle to end and then exit gracefully, I've decided to use this approach:

    public static void Main(string[] args)
    {
        var ended = new ManualResetEventSlim();
        var starting = new ManualResetEventSlim();

        AssemblyLoadContext.Default.Unloading += ctx =>
        {
            System.Console.WriteLine("Unloding fired");
            starting.Set();

            System.Console.WriteLine("Waiting for completion");
            ended.Wait();
        };

        System.Console.WriteLine("Waiting for signals");
        starting.Wait();

        System.Console.WriteLine("Received signal gracefully shutting down");
        Thread.Sleep(5000);
        ended.Set();
    }

I've tested it and it works, when calling `docker stop`, docker daemon sends `SIGTERM` signal to the process #1 of the container (which happens to be my app) and CoreCLR invokes `AssemblyLoadContext.Default.Unloading` event which is handled appropriately.

So I have a working app and a way (theoretically) how to stop it. I'm just not sure, how should I implement it for a given context. Should I send some kind of token to the `Job`? I want the run flow to be like following:

 1. The app is running normally.
 2. `SIGTERM` is received. If `Execute` isn't running, stop the app, if it is - wait for it to end.
 3. When the `Execute` ends, end the app.

How this kind of thing should be implemented? Please advice some kind of scheme or pattern to achieve that. Thanks in advance!