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:
- The app is running normally.
 SIGTERMis received. IfExecuteisn't running, stop the app, if it is - wait for it to end.- When the 
Executeends, 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!
So, I figured something out: what if I add public Task CurrentIteration { get; private set; } and change Execute(); to:
CurrentIteration = Execute();
await CurrentIteration;
and after starting.Wait(); in stopping sample, add:
job.Stop();
await job.CurrentIteration;
Will that work? Are there any issues with the following approach?