Sincronizzazione pattern MVC

0

Ho riscontrato un problema durante la sincronizzazione del mio modello e la visualizzazione dei thread

Ho una vista che è una tabella. In esso, l'utente può selezionare poche righe. Aggiorno la vista non appena l'utente fa clic su una riga qualsiasi poiché non desidero che l'interfaccia utente sia lenta. Questo aggiornamento viene eseguito da una logica che viene eseguita nella thread del controller di seguito.

Allo stesso tempo, il controller aggiorna anche i dati del modello, che si svolge in un thread diverso. Ad esempio, il controllore inserisce la query in una coda, che viene quindi eseguita dal thread del modello, che è un'interfaccia a thread singolo.

Non appena la query viene eseguita, il controller riceverà un segnale.

Ora, per mantenere la vista e il modello sincronizzati, aggiornerò nuovamente la vista in base al valore di ritorno della query (i dati restituiti dal modello), anche se ho già aggiornato la vista per quell'azione utente.

Tuttavia, sto affrontando problemi perché, richiedendo molto tempo al modello per restituire il risultato, all'epoca l'utente avrebbe eseguito più clic. Pertanto, a seguito dell'aggiornamento della vista in base alle informazioni del modello, la vista a volte torna allo stato in cui sono stati effettuati i clic precedenti

(Supponiamo che l'utente faccia clic tre volte su righe diverse. Aggiorno la vista non appena si verifica il clic. Inoltre, aggiorno la vista quando ottengo i dati dal modello, che dovrebbe essere uguale allo stato già aggiornato di la vista Ora, quando l'utente fa clic terza volta, ottengo i dati per il primo clic dal modello. Di conseguenza, la vista torna a uno stato generato dal primo clic)

C'è un modo per gestire questo problema di sincronizzazione?

    
posta Hariprasad 20.05.2014 - 19:22
fonte

3 risposte

1

Descrivi una situazione in cui l'utente aggiorna tre righe. La prima transazione viene completata dopo il terzo clic e tutte e tre le righe si aggiornano con i dati non aggiornati.

Devi pensare alla granularità degli articoli del tuo modello. Sembra che tu desideri che ogni riga dell'interfaccia utente rappresenti un singolo aggregato (limite transazionale). Quindi, quando un aggregato viene aggiornato nel database, devi solo aggiornare quella riga di aggregazione sull'interfaccia utente.

In altre parole, penso che gli eventi a cui la tua visione è vincolante siano troppo grossolani. Quando un elemento del modello viene aggiornato nel database, dovresti considerare di restituire solo il nuovo stato dell'articolo dal back-end. Poiché i comandi dall'interfaccia utente vengono gestiti a livello atomico, anche gli eventi risultanti devono essere atomici.

Pseudo-C #:

class ViewController
{
    ...
    void RowUpdatedEventHandler(RowUpdatedEvent e)
    {
        var row = this.rows[e.AggregateId];
        row.Name = e.NewName;
        row.Price = e.NewPrice;
    }
}
    
risposta data 17.11.2014 - 20:08
fonte
0

Potresti pensare di utilizzare un approccio MVVM . Hai detto che vuoi che la tua vista sia reattiva, quindi aggiorni ad ogni clic, ma poi dichiari anche che è che impiega molto tempo affinché il modello restituisca il risultato . Questi due non funzionano insieme.

Con una progettazione MVVM si ottiene un modello di visualizzazione separato (l'ultima VM), che dovrebbe essere creato in modo tale da fornire quasi istantaneamente la vista con i dati quando si fa clic su un'altra riga. Inoltre, se i dati del modello stesso vengono modificati, il modello di visualizzazione può dire due cose: 1) se i dati modificati sono anche rilevanti per la vista corrente, cioè se è attualmente visualizzata e 2) se la modifica è effettiva cambiare rispetto ai dati che ha dato la vista l'ultima volta. In entrambi i casi, la vista non deve cambiare.

Infine, si sposta l'aspetto di sincronizzazione tra i thread su un altro confine, dove gli aggiornamenti sul modello devono essere inviati al modello di vista, che può essere interrogato allo stesso tempo a causa dei clic nella vista. Ovviamente devi sincronizzare questi aggiornamenti, tuttavia, sia l'aggiornamento del modello di visualizzazione che le query dovrebbero essere molto veloci. In particolare, poiché il modello di vista non dovrebbe fare molto di più che derivare alcuni valori di stringa o simili per la visualizzazione, è molto più veloce di un utente che fa clic su diverse righe. Inoltre, le query sono abbastanza veloci da consentire il blocco nell'ascoltatore del mouse, in modo da non dover nemmeno preoccuparsi delle condizioni di gara.

tl; dr - crea un modello separato per la tua vista, che supporta query veloci e rende il blocco degli aggiornamenti di visualizzazione (cioè non asincrono).

    
risposta data 21.05.2014 - 07:01
fonte
0

La vista mantiene un riferimento all'ultimo elemento che ha causato un evento rilevante, in modo che possa decidere se visualizzare i risultati in entrata o meno:

var last_request = nil
void row_selected(any) :
    last_request = any.id
    controller.query(any.id)

void query_completed(id, result) :
    if id == last_request then
        display(result)
    else
        log(result)

(Nota: la tua vista probabilmente la memorizza già tenendo un riferimento a ciò che è attualmente selezionato.)

Questo gestirà anche il caso in cui le persone cliccano A, poi B, e poi ancora A prima che il primo risultato sia: mostra A senza apparire con B.

I doubt if this will work.

Ecco un SCCE veloce e sporco (in Java) che dovrebbe dimostrare che funziona.

public class TheView extends JPanel {
  public static void main(String[] args) {
    SwingUtilities.invokeLater( () -> {
      TheView view = new TheView();
      Controller ctrl = new Controller(view);

      JFrame frame = new JFrame();
      frame.getContentPane().add(view);
      frame.pack();
      frame.setVisible(true);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    });
  }

  JTextArea output;
  JTable table;

  public TheView() {
    super(new BorderLayout());

    table = new JTable(new Object[][] {
      {1, "Tootsie"},
      {8, "Elvis"},
      {14, "Michael"}
    }, new Object[] {
      "ID", "Name"
    });
    add(new JScrollPane(table), BorderLayout.NORTH);

    output = new JTextArea(6, 80);
    add(new JScrollPane(output));
  }

  public void addListSelectionListener(ListSelectionListener l) {
    table.getSelectionModel().addListSelectionListener(l);
  }

  public Object getValueAtSelected(int column) {
    int idx = table.getSelectedRow();
    if ( idx < 0 ) return null;
    return table.getValueAt(idx, column);
  }

  public void addOutput(String str) {
    output.append(str + '\n');
  }
}

class Controller {
  ExecutorService executor = Executors.newCachedThreadPool();
  private volatile int lastID;
  TheView view;

  public Controller(TheView view) {
    this.view = view;
    view.addListSelectionListener( (evt) -> {
      Object o = view.getValueAtSelected(0);
      if ( o instanceof Integer && (Integer) o != lastID ) {
        processRequest((Integer) o);
      }
    });
  }

  public void processRequest(int id) {
    lastID = id;
    executor.submit( () -> {
      offerReply(id, new SlowProcessor(id).call());
      return null;
    });
  }

  void offerReply(int id, String result) {
    if ( id == lastID ) {
      SwingUtilities.invokeLater( () -> view.addOutput(result) );
    }
  }
}

class SlowProcessor implements Callable<String> {
  private final int id;

  public SlowProcessor(int id) {
    this.id = id;
  }

  public String call() throws InterruptedException {
    Thread.sleep(2000);
    final String response = "I am the reply to ID " + id;
    return response;
  }
}
    
risposta data 20.05.2014 - 20:01
fonte

Leggi altre domande sui tag