Come aspettare l'esecuzione del metodo e poi uscire?

6

Ho un'applicazione console (dotnet core, ubuntu), che assomiglia al seguente:

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

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

Mentre Job è l'implementazione di 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 è definito come:

public class Job : JobAbstract
{
    public Job(periodMs) : base(periodMs)
    {}

    public override async Task Execute()
    {
        await SomeTask();
        OtherKindOfJob();
        await MaybeMoreAsyncTasks();
        // etc
    }
}

Funziona tutto bene, come ci si potrebbe aspettare.

Ora sto avvolgendo tutto in contenitori docker per la consegna continua. docker stop può essere eseguito su un contenitore mentre viene eseguito il metodo Execute di Job . Per aspettare che il ciclo termini e poi uscire con grazia, ho deciso di utilizzare questo approccio:

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();
}

L'ho provato e funziona, quando si chiama docker stop , il daemon docker invia il segnale SIGTERM al processo # 1 del contenitore (che è la mia app) e CoreCLR richiama l'evento AssemblyLoadContext.Default.Unloading che è gestito in modo appropriato.

Quindi ho un'app funzionante e un modo (teoricamente) come fermarlo. Non sono sicuro, come dovrei implementarlo per un determinato contesto. Dovrei inviare qualche tipo di token al Job ? Voglio che il flusso di corsa sia come segue:

  1. L'app è in esecuzione normalmente.
  2. SIGTERM è ricevuto. Se Execute non è in esecuzione, interrompi l'app, se è presente, attendi che termini.
  3. Quando termina% co_de, termina l'app.

Come dovrebbe essere implementato questo genere di cose? Si prega di consigliare qualche tipo di schema o schema per raggiungere questo obiettivo. Grazie in anticipo!

Quindi ho capito qualcosa: cosa succede se aggiungo Execute e cambio public Task CurrentIteration { get; private set; } in:

CurrentIteration = Execute();
await CurrentIteration;

e dopo Execute(); nell'arresto dell'esempio, aggiungere:

job.Stop();
await job.CurrentIteration;

Funzionerà? Ci sono problemi con il seguente approccio?

    
posta nicks 12.12.2016 - 07:12
fonte

1 risposta

4

Considerando il tuo codice, presumo che l'intera logica della tua applicazione possa essere espressa come singolo Task Execute() . E quello di cui hai bisogno è uno schema di cancellazione delle attività piuttosto semplice.

Il tuo codice potrebbe essere simile a:

public static void Main(string[] args)
{
    Start();
}

public static async void Start()
{
    var cts = new CancellationTokenSource();
    var starting = new ManualResetEventSlim();

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

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

    // run application
    try
    {
        await Execute(cts.Token);
    }
    catch(TaskCancellationException ex)
    {
        // cancelled
    }

    ended.Set();
}

Quello che non mi piace è come gestisci il "segnale" di arresto dalla finestra mobile. Non mi piace il fatto che in pratica "blocchi" l'evento Unloading finché l'applicazione non si chiude. Ma non so se c'è un altro modo in cui non ho esperienza con la finestra mobile e non ho mai dovuto gestire l'arresto "grazioso" quando l'applicazione è terminata con SIGTERM . Modifica: guardando questo , il tuo approccio sembra essere solo un modo reale di gestirlo. C'è anche un ragazzo che lo fa allo stesso modo che suggerisco.

    
risposta data 12.12.2016 - 08:15
fonte

Leggi altre domande sui tag