Entonces, algo me ha estado molestando sobre el nuevo soporte asíncrono en C # 5:
El usuario presiona un botón que inicia una operación asíncrona. La llamada regresa inmediatamente y la bomba de mensajes comienza a funcionar nuevamente; ese es el punto.
Por lo tanto, el usuario puede presionar el botón nuevamente, lo que provoca un reencuentro. ¿Qué pasa si esto es un problema?
En las demostraciones que he visto, deshabilitan el botón antes de la await
llamada y lo habilitan nuevamente después. Esto me parece una solución muy frágil en una aplicación del mundo real.
¿Deberíamos codificar algún tipo de máquina de estado que especifique qué controles deben deshabilitarse para un conjunto dado de operaciones en ejecución? ¿O hay un mejor camino?
Estoy tentado de mostrar un diálogo modal durante la operación, pero esto se parece un poco a usar un mazo.
¿Alguien tiene alguna idea brillante, por favor?
EDITAR:
Creo que deshabilitar los controles que no deberían usarse mientras se ejecuta una operación es frágil porque creo que se volverá complejo rápidamente cuando tenga una ventana con muchos controles. Me gusta simplificar las cosas porque reduce la posibilidad de errores, tanto durante la codificación inicial como en el mantenimiento posterior.
¿Qué sucede si hay una colección de controles que deberían deshabilitarse para una operación en particular? ¿Y qué pasa si se ejecutan varias operaciones simultáneamente?
fuente
Respuestas:
Comencemos señalando que esto ya es un problema, incluso sin soporte asíncrono en el idioma. Muchas cosas pueden hacer que los mensajes se retiren mientras se maneja un evento. Usted ya tiene a la defensiva código para esta situación; la nueva característica del lenguaje solo lo hace más obvio , que es la bondad.
La fuente más común de un bucle de mensajes que se ejecuta durante un evento que causa reencuentro no deseado es
DoEvents
. Lo bueno de loawait
contrarioDoEvents
es queawait
hace que sea más probable que las nuevas tareas no vayan a "matar de hambre" las tareas que se ejecutan actualmente.DoEvents
bombea el bucle de mensajes y, a continuación, invoca sincrónicamente el controlador de eventos reentrante, mientras queawait
normalmente pone en cola la nueva tarea para que se ejecute en algún momento en el futuro.Puede administrar esa complejidad de la misma manera que manejaría cualquier otra complejidad en un lenguaje OO: abstrae los mecanismos en una clase y hace que la clase sea responsable de implementar correctamente una política . (Trate de mantener los mecanismos lógicamente distintos de la política; debería ser posible cambiar la política sin modificar demasiado los mecanismos).
Si tiene un formulario con muchos controles que interactúan de manera interesante, entonces está viviendo en un mundo de complejidad creado por usted mismo . Tienes que escribir código para gestionar esa complejidad; si no te gusta, simplifica el formulario para que no sea tan complejo.
Los usuarios odiarán eso.
fuente
¿Por qué crees que deshabilitar el botón antes
await
y luego volver a habilitarlo cuando finaliza la llamada es frágil? ¿Es porque le preocupa que el botón nunca se vuelva a habilitar?Bueno, si la llamada no regresa, entonces no puede volver a hacer la llamada, por lo que parece que este es el comportamiento que desea. Esto indica un estado de error que debería corregir. Si se agota el tiempo de espera de su llamada asíncrona, esto podría dejar su aplicación en un estado indeterminado, de nuevo en el que tener un botón habilitado podría ser peligroso.
El único momento en que esto podría complicarse es si hay varias operaciones que afectan el estado de un botón, pero creo que esas situaciones deberían ser muy raras.
fuente
No estoy seguro de si esto se aplica a usted, ya que generalmente uso WPF, pero veo que hace referencia a una
ViewModel
para que así sea.En lugar de deshabilitar el botón, establezca una
IsLoading
bandera entrue
. Entonces, cualquier elemento de la interfaz de usuario que desee deshabilitar puede vincularse a la bandera. El resultado final es que se convierte en el trabajo de la interfaz de usuario ordenar su propio estado habilitado, no su lógica de negocios.Además de eso, la mayoría de los botones en WPF están vinculados a un
ICommand
, y generalmente configuro elICommand.CanExecute
igual a!IsLoading
, por lo que automáticamente evita que el comando se ejecute cuando IsLoading es igual a verdadero (también deshabilita el botón para mí)fuente
No hay nada frágil en esto, y es lo que el usuario esperaría. Si el usuario necesita esperar antes de presionar el botón nuevamente, haga que esperen.
Puedo ver cómo ciertos escenarios de control podrían hacer que decidir qué controles deshabilitar / habilitar en cualquier momento sea bastante complejo, pero ¿ es sorprendente que administrar una IU compleja sea complejo? Si tiene demasiados efectos secundarios atemorizantes de un control en particular, siempre puede deshabilitar todo el formulario y lanzar un "zumbido" sobre todo. Si este es el caso en cada control, entonces su IU probablemente no esté muy bien diseñada en primer lugar (acoplamiento apretado, agrupación de control deficiente, etc.).
fuente
Deshabilitar el widget es la opción menos compleja y propensa a errores. Lo deshabilita en el controlador antes de comenzar la operación asíncrona y lo vuelve a habilitar al final de la operación. Por lo tanto, el deshabilitar + habilitar para cada control se mantiene local para el manejo de ese control.
Incluso puede crear un contenedor, que tomará un delegado y control (o más de ellos), deshabilitará los controles y ejecutará algo de forma asincrónica, que hará el delegado y luego volverá a habilitar los controles. Debe ocuparse de las excepciones (habilite finalmente), por lo que aún funciona de manera confiable si la operación falla. Y puede combinarlo con agregar mensajes en la barra de estado, para que el usuario sepa lo que está esperando.
Es posible que desee agregar algo de lógica para contar las deshabilitaciones, por lo que el sistema sigue funcionando si tiene dos operaciones, que deberían deshabilitar una tercera, pero que no son mutuamente excluyentes. Deshabilita el control dos veces, por lo que volverá a habilitarse solo si lo vuelve a habilitar dos veces. Eso debería cubrir la mayoría de los casos mientras se mantienen separados tanto como sea posible.
Por otro lado, cualquier máquina de estado se volverá compleja. En mi experiencia, todas las máquinas de estado que vi fueron difíciles de mantener y su máquina de estado necesitaría abarcar todo el diálogo, uniendo todo a todo.
fuente
Aunque Jan Hudec y Rachel han expresado ideas relacionadas, sugeriría usar algo como una arquitectura de vista de modelo : expresar el estado del formulario en un objeto y vincular los elementos del formulario a ese estado. Esto deshabilitaría el botón de una manera que pueda escalar para escenarios más complejos.
fuente