Come impedire che gli overflow dello stack si verifichino in questa struttura di comando con callback?

2

Ho una serie di lavori che ho bisogno di eseguire. L'entità che gestisce questi lavori è responsabile della composizione dei lavori e dell'avvio. Al termine di un lavoro, il lavoro utilizza una funzione di callback per informare l'entità del risultato del lavoro (se ha generato un'eccezione e tale)

public class TaskSet {
    Queue<Job> JobsToExecute;
    void executeJob(Job j);
    void jobFinishedCallback(Job j);
}

public class Job {
    Action<Job> jobFinishedCallback();
    public void Execute() { jobFinishedCallback(this); }
}

Quando un lavoro ha fallito, jobFinishedCallback chiama jobFinishedCallback del TaskSet. Se il lavoro non è riuscito, jobFinishedCallback in TaskSet richiamerà nuovamente executeJob. Se un lavoro fallisce 100 volte, lo stack delle chiamate aumenta di conseguenza. Execute -> jobFInishedCallback -> executeJob -> e così via.

Ho aggiunto un numero massimo di tentativi, ma mi sento ancora a disagio nel caso di un grosso stack se un lavoro continua a non funzionare, credo che sia un odore di codice.

Qualcuno ha un buon suggerimento per evitare questo problema?

    
posta Zimano 01.05.2018 - 13:23
fonte

2 risposte

5

I have a set of jobs that I need executed.

eccellente. Hai scelto un'architettura ragionevole per soddisfare questa esigenza.

When a job is finished, the job uses a callback function to inform the entity of the result of the job (Whether it threw an exception and such)

Congratulazioni, hai reinventato Task<T> e lo hai rinominato Job . Fare la stessa cosa di una libreria consolidata è la prova che sei sulla strada giusta, ma è anche la prova che potresti ripetere il lavoro che è già stato fatto.

Forse basta usare Task<T> , se soddisfa le tue esigenze. Tieni presente che un'attività ha una funzione "continua con" in cui puoi assegnare la richiamata direttamente all'attività, proprio come fa Job .

Inoltre: C # consente di creare flussi di lavoro asincroni basati sulle attività molto facilmente utilizzando await . L'asincronia basata sulla callback ti fa girare la logica del programma al suo interno e rende difficile la gestione delle eccezioni. Lascia che il compilatore esegua il sollevamento pesante . Riscriverà i tuoi flussi di lavoro nello stile di passaggio continuo per te e otterrai la gestione delle eccezioni corretta.

If the job failed, jobFinishedCallback in TaskSet will call executeJob again.

Questa è la cosa sbagliata da fare, come hai scoperto.

Does anyone have a good suggestion to avoid this problem?

Non chiamare di nuovo executeJob . Accoda nuovamente il lavoro nella coda di lavoro e ritorna . In questo modo non solo non ottieni una regressione infinita, ma tratti anche il tentativo abbastanza . Il tentativo dovrebbe aspettare il suo turno; tutti i lavori in coda ora dovrebbero avere la priorità su di esso.

Allo stesso modo, quando un lavoro termina normalmente, non chiamarne direttamente la continuazione; puoi incontrare lo stesso problema. Accoda un nuovo lavoro che chiama la continuazione e dà a quel lavoro una continuazione nulla. E ancora, questo è più giusto; la continuazione dovrebbe aspettare il suo turno, proprio come ogni altro lavoro in coda attende il suo turno.

Più in generale: oggi sarebbe un buon giorno per fare qualche ricerca su compiti / futuri / promesse, programmazione reattiva, sequenze osservabili, trampolini, stile di passaggio continuo, modello di attore e così via. Stai reinventando da zero le tecnologie che gli esperti hanno studiato per decenni, quindi impara dalle loro conoscenze accumulate , piuttosto che le tue prove ed errori. E usa le classi testate, debuggate e ben progettate nella libreria , invece di farle da sola - purché soddisfino le tue esigenze. In caso contrario, si prega di fornire un feedback a Microsoft sul motivo per cui non lo fanno.

Le attività C # usano il concetto di "contesto di attività" per determinare come sono programmate le continuazioni; la tua re-invenzione è fondamentalmente le regole di contesto per le applicazioni GUI. La coda dei messaggi di Windows viene utilizzata come coda di lavoro. Ci sono altre scelte che sono possibili; per esempio, alcuni contesti cattureranno invece un thread di lavoro dal pool ed eseguiranno la continuazione sul thread. Usando le parti già pronte già create puoi sfruttare la flessibilità e la potenza già progettate nel sistema.

    
risposta data 01.05.2018 - 19:19
fonte
1

Non avere una richiamata. o almeno non passare il lavoro in esso.

TaskSet.executeJob(Job j)
{
    j.Execute();    
    //the job has finished
    if(j.status == failed)
    {
        this.Queue.Add(j); //queue for retry
    }
}
    
risposta data 01.05.2018 - 13:39
fonte

Leggi altre domande sui tag