design per operazioni interrompibili

6

Non sono riuscito a trovare un argomento migliore ma eccolo qui;

1) Quando l'utente fa clic su un pulsante, il codice inizia a funzionare,

2) Quando si fa clic su un altro pulsante, si interrompe l'esecuzione di qualsiasi operazione e inizia a eseguire il codice del secondo pulsante,

3) O con l'interazione dell'utente, è stata rilevata un'interruzione dell'alimentazione elettrica da un dispositivo collegato, pertanto il nostro software annullerebbe l'evento corrente e avvierà la procedura di spegnimento.

In che modo questo disegno si applica principalmente al codice? Intendo dire "smettila di fare quello che stai facendo"?

Se vuoi dire eventi, gestori di eventi, ecc. come leghi una condizione all'evento? e come si dice al programma senza utilizzare ladder se si vuole terminare il processo?

method1();
if (powerdown) return;
method2(); 
if (powerdown) return;

ecc.

    
posta tpaksu 15.12.2012 - 22:05
fonte

5 risposte

3

Un approccio che fornirà codice stabile consiste nell'utilizzare una macchina di stato . Quindi è meglio distinguere:

  • eventi
  • processi / attività in corso
  • stato del tuo programma (cioè inattivo, eseguendo un'attività, spegnendo)

Ecco un esempio molto semplice e informale (in pseudo-codice):

enum States = {idle, runningTask, poweringDown, stopped}
enum events = {button1, button2, powerDown, stop}

currentState = States.idle;
currentTask = nil;

// simple state engine. make it a critical section for thread safety
synchronized processEvent(event) {
  switch(currentState) {
    case States.runningTask:
       currentTask.stop();
       currentState = nil;
       currentTask = nil;
       break;
    default: 
   }

  switch(event) {
    case events.button1: 
         if(currentState != States.idle) break;
         currentTask = task1.start();
         currentState = States.runningTask;
         break;
    case events.button2:
         if(currentState != States.idle) break;
         currentTask = task2.start();
         currentState = States.runningTask;
         break;
    case events.powerDown:
         if(currentState != States.idle) break;
         currentTask = powerdown.start();
         currentState = States.poweringDown;
         break;
    case events.stop:
         if(!powerdown.finished())
           exception("stop received before power down");
         currentTask = nil;
         currentState = stopped;
         break;
    default: 
         exception("event unknown");
  }
}

// onButton1Pressed will call processEvent(events.button1)
// onButton2Pressed will call processEvent(events.button2)

// task1, task2, powerdown are assumed to implement some form of thread protocol,
//i.e. start() returns an object referencing the new thread, stop() kills the thread  
    
risposta data 20.12.2012 - 01:16
fonte
2

Se consideri un programma multi-thread, dovrebbe essere possibile implementare alcuni controlli per ogni thread e le rispettive operazioni.

Per quanto riguarda la parte "smetti cosa stai facendo": se stai consumando una risorsa (connessione a un DB, file, ecc.) e smetti di manipolare i dati bruscamente devi assicurarti che la tua applicazione sia affidabile, devi usare una API di transazione .NET per rendere l'operazione atomica e controllare la coda delle operazioni nel caso in cui il processo si interrompa, altrimenti si finirà con dati corrotti e la propria applicazione diventerà incoerente. link

Ricordare: non è una buona pratica affidarsi alle eccezioni per il controllo del flusso, quindi collegare un componente di monitoraggio alla propria applicazione e agire ogni volta che si identifica che il server connesso (come menzionato nella domanda) non funziona. ad esempio, link

    
risposta data 19.12.2012 - 20:42
fonte
1

F # (un altro linguaggio .NET) ha un flusso di lavoro asincrono che si prende cura del flusso e della cancellazione per te dietro le quinte, offrendoti un modo semplice e facile per codificare i tuoi compiti.

Supponendo che i tuoi compiti siano suddivisi in operazioni più piccole (che restituiscono loro stessi async - potresti doverli raggruppare) potresti fare qualcosa del tipo:

let button1Task arg =
    async {
        let! r1 = someAsyncComputation(arg)
        do! Async.Sleep(500)
        let! r2 = someOtherAsyncComputation(r1)
        return r2
    }

Quindi avvia l'operazione in questo modo:

let asy = button1Task someValue
let cts = new CancellationTokenSource()
Async.Start(asy, cts.Token)

Quindi, quando vuoi fermarlo, fai:

cts.Cancel()

e si fermerà (credo dopo aver terminato qualsiasi operazione che stava eseguendo - cioè quando colpisce il prossimo do! o let! ). Non penso che la chiamata a Cancel() stia bloccando, ma potrebbe esserci una versione diversa o un modo per controllare e assicurarsi che il calcolo sia stato interrotto.

Nello scenario dei pulsanti, è possibile che ciascun pulsante o evento di alimentazione verifichi le attività attualmente in esecuzione e le annulli prima di iniziare la propria attività.

La differenza tra questo e i thread è che non dovresti fare la tua ladder se è e potresti comunque abortire in sicurezza l'attività (senza dover ricorrere a Thread.Abort() ).

Potresti riuscire a trovare qualcosa di simile per C #, ma probabilmente non verrà codificato come bello.

Ecco alcune letture:

Sto ancora imparando da solo su queste cose quindi questa potrebbe non essere la migliore spiegazione.

    
risposta data 21.12.2012 - 15:08
fonte
0

Se stai mirando a uno scenario multithreading, puoi utilizzare Task (.NET 4.0), o BackgroundWorker (.NET 2.0) che supporta la cooperativa cancellazione.

Naturalmente richiede anche una certa gestione nella logica dell'applicazione. Questa gestione è limitata alla verifica del flag di cancellazione e alla pulizia (e alla restituzione) in caso di cancellazione.

Il supporto dell'annullamento (senza segnali da altri thread come Task, BackgroundWorker), è limitato alla gestione delle condizioni di errore e alla pulizia / interruzione.

    
risposta data 20.12.2012 - 04:26
fonte
0

Un'alternativa ai thread utilizza un meccanismo di evento (interrupt) accoppiato con un "ciclo principale" che esegue il pulsante e il codice di spegnimento e un intervallo di timer come "scheduler" per capire quale pezzo di codice da eseguire successivamente. In questo caso ci sono tre tipi di eventi che devi affrontare:

  • pulsante1 o pulsante2 premuto
  • segnale di spegnimento
  • segnale del timer, in un intervallo di dire ogni 500 ms

L'idea è la seguente:

  1. ogni volta che viene premuto un pulsante, ricorda quale pulsante era
  2. ogni volta che scatta il timer, controlla quale parte del codice evento (pulsante) deve essere eseguito successivamente
  3. esegui il codice evento come parte del ciclo principale
  4. dividere il codice evento in più parti, dopo ogni pezzo controllare se si è verificato il segnale di spegnimento. Se è così, esci.

Nel gestore del timer, controlla quale parte del codice evento deve essere eseguita successivamente, in base a quale pulsante è stato visualizzato nel passaggio 1. Questo è lo "scheduler". In caso di segnale di spegnimento, ricordarsi di non eseguire alcun altro codice pulsante e, nel ciclo principale, eseguire invece il codice di spegnimento.

Il ciclo principale fa esattamente come suggerisce il nome:

    interrupted = false; // assigned true on alternative button pressed
    stopped = false; // assigned true at the end of power down 
    while(!stopped) {   
       while(!interrupted) {
         run the code (piece of event code) determined by the scheduler
       }
   }

EDIT: spiega l'esempio in modo più dettagliato:

Wait, è proprio uguale all'esempio OP, ma più complicato!? No. Qui la gestione, la programmazione e l'esecuzione degli eventi sono eliminati e non inquinano il "codice dei pulsanti". Piuttosto, il codice del pulsante viene mantenuto semplice e non "conosce" alcuna partizione, né è necessario prevedere una logica di "cancellazione". Si noti che la suddivisione del codice evento significa che ogni pezzo è essenzialmente un metodo semplice e separato la cui esecuzione viene attivata dal ciclo principale. Non ci sono "se-ladder" come nell'esempio di OP. Ciò mantiene la logica reale leggibile e mantenibile.

Cosa succede se il codice evento deve essere eseguito una sola volta? Lo scheduler può anche essere esteso per consentire al codice evento, o ad alcune sue parti, di essere eseguito una sola volta. Per questo introdurre una struttura dati che tiene traccia di quale pezzo è stato eseguito. Ogni volta che viene eseguito lo scheduler, aggiorna la struttura dei dati e, in base alle condizioni impostate per ogni parte di codice, decide se deve essere pianificato o eseguito.

Perché non eseguire il codice del pulsante direttamente in ogni gestore di eventi? È anche possibile eseguire il codice del pulsante direttamente nel passaggio 1, ma ciò renderebbe l'interfaccia utente lenta, poiché non risponderebbe fino a quando il codice non sarà terminato. Questo è il motivo per cui il gestore del timer ha bisogno di implementare un programmatore. Determina quale codice eseguire. Il codice effettivo può essere eseguito solo sul loop principale.

Perché non eseguire il codice del pulsante all'interno del gestore del timer? È anche possibile eseguire il codice dei pulsanti direttamente all'interno del gestore del timer. Lo svantaggio è che dovresti assicurarti che il codice non superi la durata dell'intervallo del timer per evitare interruzioni eccessive o peggio.

Non è troppo complesso ? Questo progetto può sembrare eccessivamente complicato all'inizio, ma garantisce che il sistema continui a funzionare senza intoppi separando UI, pianificazione e esecuzione effettiva del codice. L'alternativa, ovviamente, è usare i thread, ma poi il punto centrale di questa risposta è mostrare una soluzione withouth threads.

Ci sono miglioramenti, ad es. per i vincoli in tempo reale? In sostanza, tutto questo si riduce a un problema di pianificazione. Vedi Scheduling Algorithms (Wikipedia) per approcci più sofisticati, ad es. per gestire i requisiti in tempo reale.

    
risposta data 21.12.2012 - 09:46
fonte