Soluzioni per re-entrancy asincrona C # 5

16

Quindi, qualcosa mi ha infastidito del nuovo supporto asincrono in C # 5:

L'utente preme un pulsante che avvia un'operazione asincrona. La chiamata ritorna immediatamente e il messaggio pompa ricomincia a funzionare - questo è il punto.

Quindi l'utente può premere di nuovo il pulsante, causando un nuovo rientro. Cosa succede se questo è un problema?

Nelle demo che ho visto, disattivano il pulsante prima della chiamata await e lo abilitano nuovamente in seguito. Mi sembra una soluzione molto fragile in un'app reale.

Dovremmo codificare una sorta di macchina a stati che specifica quali controlli devono essere disabilitati per un dato insieme di operazioni in esecuzione? O c'è un modo migliore?

Sono tentato di mostrare una finestra di dialogo modale per la durata dell'operazione, ma mi sembra un po 'come usare un martello.

Qualcuno ha idee brillanti, per favore?

EDIT:

Penso che disabilitare i controlli che non dovrebbero essere usati mentre un'operazione è in esecuzione è fragile perché penso che diventerà rapidamente complesso quando si ha una finestra con molti controlli su di essa. Mi piace mantenere le cose semplici perché riduce la possibilità di bug, sia durante la codifica iniziale che la successiva manutenzione.

Che cosa succede se c'è una collezione di controlli che dovrebbero essere disabilitati per una particolare operazione? E cosa succede se più operazioni vengono eseguite contemporaneamente?

    
posta Nicholas Butler 10.10.2011 - 13:50
fonte

6 risposte

14

So, something's been bugging me about the new async support in C# 5: The user presses a button which starts an async operation. The call returns immediately and the message pump starts running again - that's the whole point. So the user can press the button again - causing re-entrancy. What if this is a problem?

Iniziamo osservando che questo è già un problema, anche senza il supporto asincrono nella lingua. Un sacco di cose possono causare la rimozione della coda dei messaggi mentre gestisci un evento. già devi codificare in modo difensivo per questa situazione; la nuova funzione del linguaggio rende solo più ovvio , che è la bontà.

La fonte più comune di un loop di messaggi in esecuzione durante un evento che causa riacutizzazione indesiderata è DoEvents . La cosa bella di await rispetto a DoEvents è che await rende più probabile che le nuove attività non "affoghino" le attività attualmente in esecuzione. DoEvents pompa il ciclo dei messaggi e quindi richiama in modo sincrono il gestore eventi rientrante, mentre await tipicamente accoda la nuova attività in modo che possa essere eseguita in un determinato momento nel futuro.

In the demos I've seen, they disable the button before the await call and enable it again afterwards. I think disabling the controls that shouldn't be used while an operation is running is fragile because I think it will quickly become complex when you have a window with many controls on it. What if there is a collection of controls that should be disabled for a particular operation? And what if multiple operations are running concurrently?

Puoi gestire quella complessità nello stesso modo in cui gestiresti qualsiasi altra complessità in un linguaggio OO: estrai i meccanismi in una classe e rendi la classe responsabile dell'implementazione corretta di politica . (Cerca di mantenere logicamente i meccanismi distinti dalla policy, dovrebbe essere possibile modificare la policy senza armeggiare di troppo con i meccanismi.)

Se hai un modulo con molti controlli su di esso che interagiscono in modo interessante allora stai vivendo in un mondo di complessità di tua creazione . Devi scrivere codice per gestire questa complessità; se non ti piace, allora semplifica il modulo in modo che non sia così complesso.

I'm tempted to just show a modal dialog for the duration of the operation, but this feels a bit like using a sledgehammer.

Gli utenti lo odieranno.

    
risposta data 10.10.2011 - 17:17
fonte
6

Perché pensi di disabilitare il pulsante prima di await e poi riabilitarlo quando la chiamata è completa? È perché sei preoccupato che il pulsante non verrà mai riattivato?

Bene, se la chiamata non ritorna, non è possibile effettuare nuovamente la chiamata, quindi sembra che questo sia il comportamento che si desidera. Questo indica uno stato di errore che dovresti correggere. Se la tua chiamata asincrona è scaduta, potrebbe comunque lasciare l'applicazione in uno stato indeterminato, anche in questo caso in cui un pulsante attivato potrebbe essere pericoloso.

L'unica volta che questo potrebbe diventare complicato è se ci sono diverse operazioni che influenzano lo stato di un pulsante, ma penso che quelle situazioni dovrebbero essere molto rare.

    
risposta data 10.10.2011 - 14:02
fonte
5

Non sono sicuro se questo si applica a te dal momento che di solito utilizzo WPF, ma ti vedo referenziare un ViewModel così potrebbe.

Invece di disabilitare il pulsante, imposta un flag IsLoading su true . Quindi qualsiasi elemento dell'interfaccia utente che si desidera disabilitare può essere associato alla bandiera. Il risultato finale è che diventa il lavoro dell'interfaccia utente per risolvere il proprio stato abilitato, non la tua logica aziendale.

In aggiunta a ciò, la maggior parte dei pulsanti in WPF sono legati a ICommand , e di solito imposto il ICommand.CanExecute uguale a !IsLoading , quindi impedisce automaticamente l'esecuzione del comando quando IsLoading è uguale a true (anche disabilita il pulsante per me)

    
risposta data 10.10.2011 - 16:07
fonte
3

In the demos I've seen, they disable the button before the await call and enable it again afterwards. This seems to me like a very fragile solution in a real-world app.

Non c'è nulla di fragile in questo, ed è ciò che l'utente si aspetterebbe. Se l'utente ha bisogno di aspettare prima di premere nuovamente il pulsante, fallo attendere.

Riesco a vedere in che modo determinati scenari di controllo potrebbero decidere quali controlli disabilitare / abilitare in qualsiasi momento piuttosto complessi, ma è sorprendente che gestire un'interfaccia utente complessa sarebbe complesso? Se anche tu hai molti effetti secondari paurosi da un particolare controllo, puoi sempre solo disabilitare l'intero modulo e lanciare un "whirly-gig" di caricamento su tutta la faccenda. Se questo è il caso su ogni singolo controllo, allora l'interfaccia utente probabilmente non è molto ben progettata in primo luogo (accoppiamento stretto, cattivo controllo del raggruppamento, ecc.).

    
risposta data 10.10.2011 - 14:51
fonte
2

Disabilitare il widget è l'opzione meno complessa e soggetta a errori. Lo disabiliti nel gestore prima di avviare l'operazione asincrona e lo riabiliti al termine dell'operazione. Quindi l'abilitazione + disabilita per ciascun controllo è mantenuta locale per la gestione di quel controllo.

È anche possibile creare un wrapper, che prenderà un delegato e controllerà (o più di essi), disabiliterà i controlli e eseguirà in modo asincrono qualcosa, che eseguirà il delegato e riattiverà i controlli. Dovrebbe occuparsi delle eccezioni (fare finalmente l'abilitazione), quindi funziona ancora in modo affidabile se l'operazione fallisce. E puoi combinarlo con l'aggiunta di un messaggio nella barra di stato, in modo che l'utente sappia cosa sta aspettando.

Potresti voler aggiungere un po 'di logica per contare i disabilità, quindi il sistema continua a funzionare se hai due operazioni, che dovrebbero entrambe disabilitarne una terza, ma che non si escludono a vicenda. Disabilita il controllo due volte, quindi verrà riattivato solo se lo abiliti nuovamente due volte. Questo dovrebbe coprire la maggior parte dei casi mantenendo le custodie separate il più possibile.

D'altra parte qualsiasi cosa come la macchina statale sta diventando complessa. Nella mia esperienza tutte le macchine statali che ho mai visto erano difficili da mantenere e la tua macchina di stato avrebbe dovuto comprendere l'intero dialogo, legando tutto a tutto.

    
risposta data 10.10.2011 - 14:55
fonte
1

Sebbene Jan Hudec e Rachel abbiano espresso idee correlate, suggerirei di utilizzare qualcosa come un'architettura Model-View - esprimere lo stato della forma in un oggetto e legare gli elementi del form a quello stato. Ciò disabiliterà il pulsante in un modo che può scalare per scenari più complessi.

    
risposta data 10.10.2011 - 22:11
fonte

Leggi altre domande sui tag