Aquí tenemos un Grid
con a Button
. Cuando el usuario hace clic en el botón, se ejecuta un método en una clase de utilidad que obliga a la aplicación a recibir un clic en la cuadrícula. El flujo de código debe detenerse aquí y no continuar hasta que el usuario haya hecho clic en Grid
.
He tenido una pregunta similar antes aquí:
Espere hasta que el usuario haga clic en C # WPF
En esa pregunta, recibí una respuesta usando async / await que funciona, pero como voy a usarlo como parte de una API, no quiero usar async / await, ya que los consumidores tendrán que marcar sus métodos con asíncrono que no quiero.
¿Cómo escribo el Utility.PickPoint(Grid grid)
método para lograr este objetivo?
Vi esto que puede ayudar, pero no entendí completamente que se aplique aquí para ser honesto:
Bloqueo hasta que se complete un evento
Considérelo como algo así como el método Console.ReadKey () en una aplicación de consola. Cuando llamamos a este método, el flujo del código se detiene hasta que ingresamos algún valor. El depurador no continúa hasta que ingresamos algo. Quiero el comportamiento exacto para el método PickPoint (). El flujo de código se detendrá hasta que el usuario haga clic en la cuadrícula.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid x:Name="View" Background="Green"/>
<Button Grid.Row="1" Content="Pick" Click="ButtonBase_OnClick"/>
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
// do not continue the code flow until the user has clicked on the grid.
// so when we debug, the code flow will literally stop here.
var point = Utility.PickPoint(View);
MessageBox.Show(point.ToString());
}
}
public static class Utility
{
public static Point PickPoint(Grid grid)
{
}
}
fuente
Aync/Await
¿qué hay de hacer la Operación A y guardar la operación ESTADO ahora que desea que el usuario haga clic en Cuadrícula? Entonces, si el usuario hace clic en la Cuadrícula, verificará el estado si es verdadero, ¿entonces su operación solo hará lo que quiera?AutoResetEvent
no es lo que quieresvar point = Utility.PickPoint(Grid grid);
en el método Grid Click? hacer alguna operación y devolver la respuesta?Respuestas:
Tu enfoque es incorrecto. Impulsado por eventos no significa bloquear y esperar un evento. Nunca esperas, al menos siempre te esfuerzas por evitarlo. Esperar es desperdiciar recursos, bloquear subprocesos y tal vez introducir el riesgo de un punto muerto o un hilo zombie (en caso de que la señal de liberación nunca se eleve).
Debe quedar claro que bloquear un hilo para esperar un evento es un antipatrón, ya que contradice la idea de un evento.
Generalmente tiene dos opciones (modernas): implementar una API asincrónica o una API controlada por eventos. Como no desea implementar su API de forma asincrónica, le queda la API controlada por eventos.
La clave de una API controlada por eventos es que, en lugar de obligar a la persona que llama a esperar sincrónicamente un resultado o sondear un resultado, deja que la persona que llama continúe y le envíe una notificación, una vez que el resultado esté listo o la operación se haya completado. Mientras tanto, la persona que llama puede continuar ejecutando otras operaciones.
Cuando se mira el problema desde una perspectiva de subprocesos, la API controlada por eventos permite que el subproceso de llamada, por ejemplo, el subproceso de interfaz de usuario, que ejecuta el controlador de eventos del botón, pueda continuar manejando, por ejemplo, otras operaciones relacionadas con la interfaz de usuario, como representar elementos de la interfaz de usuario o manejar la entrada del usuario como el movimiento del mouse y las pulsaciones de teclas. La API controlada por eventos tiene el mismo efecto u objetivo que una API asincrónica, aunque es mucho menos conveniente.
Dado que no proporcionó suficientes detalles sobre lo que realmente está tratando de hacer, qué
Utility.PickPoint()
está haciendo realmente y cuál es el resultado de la tarea o por qué el usuario tiene que hacer clic en la `Cuadrícula, no puedo ofrecerle una mejor solución . Solo puedo ofrecer un patrón general de cómo implementar su requisito.Su flujo o la meta obviamente se divide en al menos dos pasos para que sea una secuencia de operaciones:
Grid
con al menos dos restricciones:
Esto requiere dos notificaciones para el cliente de la API para permitir la interacción sin bloqueo:
Debe permitir que su API implemente este comportamiento y restricciones exponiendo dos métodos públicos y dos eventos públicos.
Implementar / refactorizar API de utilidad
Utility.cs
PickPointCompletedEventArgs.cs
Usa la API
MainWindow.xaml.cs
MainWindow.xaml
Observaciones
Los eventos generados en un subproceso en segundo plano ejecutarán sus controladores en el mismo subproceso. Para acceder a
DispatcherObject
un elemento similar a una interfaz de usuario desde un controlador, que se ejecuta en un subproceso en segundo plano, se requiere que la operación crítica se coloque en cola paraDispatcher
usarDispatcher.Invoke
oDispatcher.InvokeAsync
para evitar excepciones entre subprocesos.Lea las observaciones sobre
DispatcherObject
para aprender más sobre este fenómeno llamado afinidad de despachador o afinidad de subprocesos.Algunas reflexiones - responde a tus comentarios
Debido a que se estaba acercando a mí para encontrar una solución de bloqueo "mejor", dado el ejemplo de las aplicaciones de consola, sentí convencerlo de que su percepción o punto de vista es totalmente erróneo.
Una aplicación de consola es algo totalmente diferente. El concepto de enhebrado es un poco diferente. Las aplicaciones de consola no tienen una GUI. Solo flujos de entrada / salida / error. No puede comparar la arquitectura de una aplicación de consola con una aplicación GUI enriquecida. Esto no funcionara. Realmente debes entender y aceptar esto.
Tampoco te dejes engañar por el aspecto . ¿Sabes lo que está pasando adentro
Console.ReadLine
? ¿Cómo se implementa ? ¿Está bloqueando el hilo principal y en paralelo lee la entrada? ¿O es solo una votación?Aquí está la implementación original de
Console.ReadLine
:Como puede ver, es una operación síncrona simple . Sondea la entrada del usuario en un bucle "infinito". Sin bloqueo mágico y continuar.
WPF se basa en un subproceso de representación y un subproceso de interfaz de usuario. Esos hilos siempre giran para comunicarse con el sistema operativo, como manejar la entrada del usuario, manteniendo la aplicación receptiva . Nunca querrá pausar / bloquear este hilo, ya que evitará que el marco realice un trabajo de fondo esencial, como responder a eventos del mouse; no desea que el mouse se congele:
esperando = bloqueo de hilos = falta de respuesta = mala experiencia de usuario = usuarios / clientes molestos = problemas en la oficina.
A veces, el flujo de la aplicación requiere esperar la entrada o completar una rutina. Pero no queremos bloquear el hilo principal.
Es por eso que la gente inventó modelos complejos de programación asincrónica, para permitir la espera sin bloquear el hilo principal y sin obligar al desarrollador a escribir código de subprocesos múltiples complicado y erróneo.
Cada marco de aplicación moderno ofrece operaciones asincrónicas o un modelo de programación asincrónica, para permitir el desarrollo de código simple y eficiente.
El hecho de que se esfuerce por resistir el modelo de programación asíncrono me muestra cierta falta de comprensión. Todo desarrollador moderno prefiere una API asincrónica sobre una síncrona. Ningún desarrollador serio se preocupa por usar la
await
palabra clave o declarar su métodoasync
. Nadie. Eres el primero con el que me encuentro que se queja de las API asincrónicas y las encuentra inconvenientes de usar.Si verificara su marco, qué objetivos resolver problemas relacionados con la interfaz de usuario o facilitar las tareas relacionadas con la interfaz de usuario, esperaría que fuera asíncrono todo el tiempo.
La API relacionada con la interfaz de usuario que no es asíncrona es un desperdicio, ya que complicará mi estilo de programación, por lo tanto, mi código, que por lo tanto es más propenso a errores y difícil de mantener.
Una perspectiva diferente: cuando reconoce que la espera bloquea el hilo de la interfaz de usuario, está creando una experiencia de usuario muy mala e indeseable ya que la interfaz de usuario se congelará hasta que termine la espera, ahora que se da cuenta de esto, ¿por qué ofrecería una API o un modelo de complemento que alienta a un desarrollador a hacer exactamente esto: ¿implementar la espera?
No sabe qué hará el complemento de terceros y cuánto tiempo llevará una rutina hasta que se complete. Esto es simplemente un mal diseño de API. Cuando su API opera en el subproceso de la interfaz de usuario, la persona que llama de su API debe poder realizar llamadas sin bloqueo.
Si niega la única solución barata o elegante, utilice un enfoque basado en eventos como se muestra en mi ejemplo.
Hace lo que desea: iniciar una rutina - esperar la entrada del usuario - continuar la ejecución - cumplir el objetivo.
Realmente intenté varias veces explicar por qué esperar / bloquear es un mal diseño de la aplicación. Una vez más, no puede comparar una interfaz de usuario de consola con una interfaz gráfica de usuario rica, donde, por ejemplo, el manejo de entrada solo es una multitud más compleja que simplemente escuchar la secuencia de entrada. Realmente no sé su nivel de experiencia y dónde comenzó, pero debe comenzar a adoptar el modelo de programación asincrónica. No sé la razón por la que intentas evitarlo. Pero no es sabio en absoluto.
Hoy en día, los modelos de programación asincrónica se implementan en todas partes, en cada plataforma, compilador, cada entorno, navegador, servidor, escritorio, base de datos, en todas partes. El modelo basado en eventos permite lograr el mismo objetivo, pero es menos conveniente usarlo (suscribirse / cancelar suscripción a / desde eventos, leer documentos (cuando hay documentos) para aprender sobre los eventos), confiando en hilos de fondo. La gestión de eventos está pasada de moda y solo debe usarse cuando las bibliotecas asíncronas no están disponibles o no son aplicables.
Como nota al margen: el .NET Framwork (.NET Standard) ofrece
TaskCompletionSource
(entre otros propósitos) proporcionar una manera simple de convertir una API existente controlada en una API asincrónica.El comportamiento (lo que experimenta u observa) es muy diferente de cómo se implementa esta experiencia. Dos cosas diferentes Es muy probable que su Autodesk utilice bibliotecas asincrónicas o funciones de lenguaje o algún otro mecanismo de subprocesamiento. Y también está relacionado con el contexto. Cuando el método que tiene en mente se ejecuta en un subproceso en segundo plano, el desarrollador puede optar por bloquear este subproceso. Tiene una muy buena razón para hacer esto o simplemente hizo una mala elección de diseño. Estás totalmente en el camino equivocado;) El bloqueo no es bueno.
(¿El código fuente de Autodesk es de código abierto? ¿O cómo sabes cómo se implementa?)
No quiero ofenderte, por favor créeme. Pero reconsidere implementar su API de forma asincrónica. Es solo en su cabeza que a los desarrolladores no les gusta usar async / await. Obviamente tienes la mentalidad equivocada. Y olvídate del argumento de la aplicación de consola: no tiene sentido;)
La API relacionada con la interfaz de usuario DEBE usar async / wait siempre que sea posible. De lo contrario, deja todo el trabajo para escribir código sin bloqueo al cliente de su API. Me obligarías a ajustar cada llamada a tu API en un hilo de fondo. O para usar un manejo de eventos menos cómodo. Créame, cada desarrollador en lugar de decorar a sus miembros
async
, en lugar de hacer el manejo de eventos. Cada vez que usa eventos, puede correr el riesgo de una posible pérdida de memoria, depende de algunas circunstancias, pero el riesgo es real y no raro cuando se programa descuidadamente.Realmente espero que entiendas por qué el bloqueo es malo. Realmente espero que decidas usar async / wait para escribir una API asincrónica moderna. Sin embargo, le mostré una forma muy común de esperar sin bloqueo, usando eventos, aunque le insto a que use async / wait.
Si no desea permitir que el complemento tenga acceso directo a los elementos de la interfaz de usuario, debe proporcionar una interfaz para delegar eventos o exponer componentes internos a través de objetos abstraídos.
La API internamente se suscribirá a los eventos de la interfaz de usuario en nombre del complemento y luego delegará el evento exponiendo un evento "envoltorio" correspondiente al cliente API. Su API debe ofrecer algunos enlaces donde el complemento puede conectarse para acceder a componentes específicos de la aplicación. Una API de complemento actúa como un adaptador o fachada para dar acceso externo a los internos.
Para permitir un cierto grado de aislamiento.
Eche un vistazo a cómo Visual Studio administra los complementos o nos permite implementarlos. Imagina que quieres escribir un complemento para Visual Studio e investiga un poco sobre cómo hacerlo. Te darás cuenta de que Visual Studio expone sus elementos internos a través de una interfaz o API. Por ejemplo, puede manipular el editor de código u obtener información sobre el contenido del editor sin acceso real al mismo.
fuente
var str = Console.ReadLine(); Console.WriteLine(str);
Qué sucede cuando ejecuta la aplicación en modo de depuración. Se detendrá en la primera línea de código y lo obligará a ingresar un valor en la interfaz de usuario de la consola y luego, después de ingresar algo y presionar Enter, ejecutará la siguiente línea e imprimirá lo que ingresó. Estaba pensando exactamente en el mismo comportamiento pero en la aplicación WPF.Personalmente, creo que esto está siendo demasiado complicado para todos, pero tal vez no entiendo completamente la razón por la que esto debe hacerse de cierta manera, pero parece que aquí se puede usar una simple verificación de bool.
En primer lugar, haga que su cuadrícula sea comprobable mediante éxito configurando las propiedades
Background
yIsHitTestVisible
, de lo contrario, ni siquiera capturará los clics del mouse.A continuación, cree un valor bool que pueda almacenar si debe ocurrir el evento "GridClick". Cuando se hace clic en la cuadrícula, verifique ese valor y realice la ejecución desde el evento de clic de la cuadrícula si está esperando el clic.
Ejemplo:
fuente
Intenté algunas cosas pero no puedo hacerlo sin él
async/await
. Porque si no lo usamos causaDeadLock
o la interfaz de usuario está bloqueada y entonces estamos habilitados para tomarGrid_Click
entrada.fuente
Puede bloquear de forma asincrónica usando un
SemaphoreSlim
:No puede, y tampoco quiere, bloquear el hilo del despachador sincrónicamente porque nunca podrá manejar el clic en el
Grid
, es decir, no puede ser bloqueado y manejar eventos al mismo tiempo.fuente
Console.Readline
bloquea , es decir, no vuelve hasta que se haya leído una línea. TuPickPoint
método no lo hace. Regresa de inmediato. Potencialmente podría bloquearse, pero no podrá manejar la entrada de la interfaz de usuario mientras tanto como escribí en mi respuesta. En otras palabras, tendría que manejar el clic dentro del método para obtener el mismo comportamiento.PickPoint
que maneje los eventos del mouse. ¿No veo a dónde vas con esto?Técnicamente es posible con
AutoResetEvent
y sinasync/await
, pero hay un inconveniente significativo:El inconveniente: si llama a este método directamente en un controlador de eventos de botón como lo hace su código de muestra, se producirá un bloqueo y verá que la aplicación deja de responder. Debido a que está utilizando el único subproceso de interfaz de usuario para esperar el clic del usuario, no puede responder a ninguna acción del usuario, incluido el clic del usuario en la cuadrícula.
Los consumidores del método deben invocarlo en otro hilo para evitar puntos muertos. Si se puede garantizar, está bien. De lo contrario, debe invocar el método de esta manera:
Puede causar más problemas a los consumidores de su API, excepto que solían administrar sus propios hilos. Por eso
async/await
fue inventado.fuente
Creo que el problema está en el diseño mismo. Si su API funciona en un elemento en particular, debe usarse en el controlador de eventos de este mismo elemento, no en otro elemento.
Por ejemplo, aquí queremos obtener la posición del evento de clic en la cuadrícula, la API debe usarse en el controlador de eventos asociado con el evento en el elemento de cuadrícula, no en el elemento del botón.
Ahora, si el requisito es manejar el clic en la Cuadrícula solo después de hacer clic en el Botón, entonces la responsabilidad del Botón será agregar el controlador de eventos en la Cuadrícula, y el evento de clic en la Cuadrícula mostrará el cuadro de mensaje y eliminará este controlador de eventos agregado por el botón para que no se active más después de este clic ... (no es necesario bloquear el hilo de la interfaz de usuario)
Solo para decir que si bloquea el hilo de la interfaz de usuario en el clic del botón, no creo que el hilo de la interfaz de usuario pueda activar el evento de clic en la cuadrícula después.
fuente
En primer lugar, el hilo de la interfaz de usuario no se puede bloquear al igual que la respuesta que recibió de su pregunta anterior.
Si puede estar de acuerdo con eso, entonces evitar async / esperar para que su cliente haga menos modificaciones es factible, e incluso no necesita ningún subproceso múltiple.
Pero si desea bloquear el hilo de la interfaz de usuario o el "flujo de código", la respuesta será que es imposible. Porque si el subproceso de la IU se bloqueó, no se puede recibir más información.
Como mencionó la aplicación de consola, solo hago una explicación simple.
Cuando ejecuta una aplicación de consola o una llamada
AllocConsole
desde un proceso que no se adjuntó a ninguna consola (ventana), se ejecutará conhost.exe que puede proporcionar una consola (ventana) y la aplicación de consola o el proceso de llamada se adjuntará a la consola ( ventana).Por lo tanto, cualquier código que escriba que pueda bloquear el hilo de la persona que llama, como
Console.ReadKey
no bloqueará el hilo de la interfaz de usuario de la ventana de la consola, cualquiera es la razón por la cual la aplicación de la consola espera su entrada pero aún puede responder a otra entrada como hacer clic con el mouse.fuente