Soluzioni per il rientro asincrono C # 5


16

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

L'utente preme un pulsante che avvia un'operazione asincrona. La chiamata ritorna immediatamente e il pump dei messaggi riprende a funzionare - questo è il punto.

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

Nelle demo che ho visto, disabilitano il pulsante prima della awaitchiamata e lo abilitano nuovamente in seguito. Questa mi sembra una soluzione molto fragile in un'app del mondo reale.

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

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

Qualcuno ha qualche idea brillante, per favore?


MODIFICARE:

Penso che disabilitare i controlli che non dovrebbero essere usati mentre un'operazione è in esecuzione sia 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.

Cosa succede se esiste una raccolta di controlli che dovrebbero essere disabilitati per una particolare operazione? E se più operazioni sono in esecuzione contemporaneamente?


Cosa c'è di fragile al riguardo? Fornisce all'utente un'indicazione che qualcosa è in fase di elaborazione. Aggiungi alcune indicazioni dinamiche che il lavoro è in fase di elaborazione e mi sembra una UI perfettamente valida.
Steven Jeuris,

PS: C # di .NET 4.5 è chiamato C # 5?
Steven Jeuris,

1
Solo curioso perché questa domanda è qui e non così? Si tratta sicuramente di un codice, quindi penso che appartenga a quello che penso ...
C. Ross,

@ C.Ross, questa è più una domanda di progettazione / UI. Non c'è davvero un punto tecnico che deve essere spiegato qui.
Morgan Herlocker,

Abbiamo un problema simile nei moduli HTML ajax in cui un utente preme il pulsante di invio, viene inviata una richiesta ajax e vogliamo quindi impedire all'utente di colpire di nuovo l'invio, la disattivazione del pulsante è una soluzione molto comune in quella situazione
Raynos,

Risposte:


14

Quindi, qualcosa mi ha infastidito riguardo al nuovo supporto asincrono in C # 5: l'utente preme un pulsante che avvia un'operazione asincrona. La chiamata ritorna immediatamente e il pump dei messaggi riprende a funzionare - questo è il punto. Quindi l'utente può premere di nuovo il pulsante, causando il rientro. E se questo fosse un problema?

Cominciamo osservando che questo è già un problema, anche senza supporto asincrono nella lingua. Molte cose possono causare la dequementazione dei messaggi durante la gestione di un evento. Devi già programmare in modo difensivo per questa situazione; la nuova funzionalità linguistica rende questo più ovvio , il che è bontà.

La fonte più comune di un ciclo di messaggi in esecuzione durante un evento che causa rientri indesiderati è DoEvents. La cosa bella awaitrispetto a ciò DoEventsè che awaitrende più probabile che i nuovi compiti non stiano per "morire di fame" i compiti attualmente in esecuzione. DoEventspompa il ciclo di messaggi e quindi richiama in modo sincrono il gestore di eventi rientranti, mentre in awaitgenere accoda la nuova attività in modo che possa essere eseguita in futuro.

Nelle demo che ho visto, disabilitano il pulsante prima della chiamata in attesa e lo abilitano nuovamente in seguito. Penso che disabilitare i controlli che non dovrebbero essere usati mentre un'operazione è in esecuzione sia fragile perché penso che diventerà rapidamente complesso quando si ha una finestra con molti controlli su di essa. Cosa succede se esiste una raccolta di controlli che dovrebbero essere disabilitati per una particolare operazione? E se più operazioni sono in esecuzione contemporaneamente?

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

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

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

Gli utenti lo odieranno.


Grazie Eric, immagino che questa sia la risposta definitiva - dipende dallo sviluppatore dell'applicazione.
Nicholas Butler,

6

Perché pensi che disabilitare il pulsante prima di awaite quindi riattivarlo al termine della chiamata sia fragile? È 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 desiderato. Questo indica uno stato di errore che dovresti correggere. Se la tua chiamata asincrona scade, potrebbe comunque lasciare la tua applicazione in uno stato indeterminato, di nuovo uno in cui avere un pulsante abilitato potrebbe essere pericoloso.

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


Ovviamente devi gestire gli errori - ho aggiornato la mia domanda
Nicholas Butler il

5

Non sono sicuro che questo valga per te poiché di solito uso WPF, ma ti vedo fare riferimento a ViewModelcosì potrebbe.

Invece di disabilitare il pulsante, imposta un IsLoadingflag su true. Quindi qualsiasi elemento dell'interfaccia utente che si desidera disabilitare può essere associato al flag. Il risultato finale è che diventa compito dell'interfaccia utente risolvere il proprio stato abilitato, non la propria logica di business.

Inoltre, la maggior parte dei pulsanti in WPF sono associati a un ICommand, e di solito ho impostato ICommand.CanExecuteuguale a !IsLoading, quindi impedisce automaticamente l'esecuzione del comando quando IsLoading è uguale a true (disabilita anche il pulsante per me)


3

Nelle demo che ho visto, disabilitano il pulsante prima della chiamata in attesa e lo abilitano nuovamente in seguito. Questa mi sembra una soluzione molto fragile in un'app del mondo reale.

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

Posso vedere come alcuni scenari di controllo potrebbero rendere piuttosto complessa la decisione di quali controlli disabilitare / abilitare in qualsiasi momento, ma è sorprendente che la gestione di un'interfaccia utente complessa sia complessa? Se hai troppi effetti collaterali spaventosi da un particolare controllo, puoi sempre semplicemente disabilitare l'intero modulo e lanciare un "whirly-concerto" sull'intera faccenda. Se questo è il caso di ogni singolo controllo, probabilmente la tua IU non è molto ben progettata in primo luogo (accoppiamento stretto, raggruppamento di controllo scadente, ecc.).


Grazie - ma come gestire la complessità?
Nicholas Butler,

Un'opzione sarebbe quella di mettere i relativi controlli nel container. Disabilitare / abilitare dal contenitore di controllo, non dal controllo. vale a dire: EditorControls.Foreach (c => c.Enabled = false);
Morgan Herlocker,

2

La disabilitazione del widget è l'opzione meno complessa e soggetta a errori. Lo si disabilita nel gestore prima di avviare l'operazione asincrona e si riattiva al termine dell'operazione. Pertanto, disabilitare + abilita per ciascun controllo viene mantenuto locale per la gestione di quel controllo.

Puoi persino creare un wrapper, che prenderà un delegato e controllerà (o più di essi), disabiliterà i controlli ed eseguirà in modo asincrono qualcosa, che farà il delegato e che riattiverà i controlli. Dovrebbe occuparsi delle eccezioni (eseguire finalmente l'abilitazione), quindi funziona in modo affidabile se l'operazione non riesce. E puoi combinarlo con l'aggiunta di messaggi nella barra di stato, in modo che l'utente sappia cosa sta aspettando.

Potresti voler aggiungere un po 'di logica per contare le disabilitazioni, quindi il sistema continua a funzionare se hai due operazioni, che dovrebbero disabilitarne una terza, ma che non si escludono a vicenda. Disabiliti il ​​controllo due volte, quindi verrà nuovamente abilitato solo se lo riattivi due volte. Ciò dovrebbe riguardare la maggior parte dei casi mantenendo separati i casi il più possibile.

D'altra parte qualcosa come la macchina a stati diventerà complesso. Nella mia esperienza, tutte le macchine a stati che ho mai visto erano difficili da mantenere e la tua macchina a stati avrebbe dovuto abbracciare l'intero dialogo, legando tutto a tutto.


Grazie - mi piace questa idea. Quindi avresti un oggetto manager per la tua finestra che conta il numero di richieste di disabilitazione e abilitazione per ciascun controllo?
Nicholas Butler,

@NickButler: Bene, hai già un oggetto manager per ogni finestra: il modulo. Quindi avrei una classe che implementa le richieste e il conteggio e aggiunge semplicemente istanze ai moduli pertinenti.
Jan Hudec,

1

Sebbene Jan Hudec e Rachel abbiano espresso idee correlate, suggerirei di usare qualcosa come un'architettura Model-View : esprimere lo stato della forma in un oggetto e legare gli elementi della forma a quello stato. Ciò disabiliterebbe il pulsante in modo tale da adattarsi a scenari più complessi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.