En mi aplicación de metro C # / XAML, hay un botón que inicia un proceso de larga duración. Entonces, como se recomienda, estoy usando async / await para asegurarme de que el hilo de la interfaz de usuario no se bloquee:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
await GetResults();
}
private async Task GetResults()
{
// Do lot of complex stuff that takes a long time
// (e.g. contact some web services)
...
}
Ocasionalmente, las cosas que suceden dentro de GetResults requerirían una entrada adicional del usuario antes de poder continuar. Para simplificar, digamos que el usuario solo tiene que hacer clic en el botón "continuar".
Mi pregunta es: ¿cómo puedo suspender la ejecución de GetResults de tal manera que aguarde un evento como el clic de otro botón?
Aquí hay una forma fea de lograr lo que estoy buscando: el controlador de eventos para el botón continuar "establece una bandera ...
private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
_continue = true;
}
... y GetResults lo sondea periódicamente:
buttonContinue.Visibility = Visibility.Visible;
while (!_continue) await Task.Delay(100); // poll _continue every 100ms
buttonContinue.Visibility = Visibility.Collapsed;
La encuesta es claramente terrible (espera ocupada / pérdida de ciclos) y estoy buscando algo basado en eventos.
¿Algunas ideas?
Por cierto, en este ejemplo simplificado, una solución sería dividir GetResults () en dos partes, invocar la primera parte desde el botón de inicio y la segunda parte desde el botón continuar. En realidad, las cosas que suceden en GetResults son más complejas y se pueden requerir diferentes tipos de entrada del usuario en diferentes puntos dentro de la ejecución. Por lo tanto, dividir la lógica en múltiples métodos no sería trivial.
ManualResetEvent(Slim)
no parece apoyarWaitAsync()
.async
no significa "se ejecuta en un hilo diferente", o algo así. Simplemente significa "puede usarawait
en este método". Y en este caso, el bloqueo internoGetResults()
realmente bloquearía el hilo de la interfaz de usuario.await
en sí mismo no garantiza que se cree otro subproceso, pero hace que todo lo demás después de la declaración se ejecute como una continuación de laTask
llamada o en espera de que la llameawait
. La mayoría de las veces, es una especie de operación asincrónica, que podría ser la finalización de E / S, o algo que está en otro hilo.SemaphoreSlim.WaitAsync
no solo empuja elWait
hilo a un hilo de grupo de hilos.SemaphoreSlim
tiene una cola adecuada deTask
s que se utilizan para implementarWaitAsync
.Cuando tiene algo inusual que necesita
await
, la respuesta más fácil es a menudoTaskCompletionSource
(o algunaasync
primitiva basada enTaskCompletionSource
).En este caso, su necesidad es bastante simple, por lo que puede usar
TaskCompletionSource
directamente:Lógicamente,
TaskCompletionSource
es como unasync
ManualResetEvent
, excepto que solo puede "configurar" el evento una vez y el evento puede tener un "resultado" (en este caso, no lo estamos usando, así que simplemente configuramos el resultado ennull
).fuente
Aquí hay una clase de utilidad que uso:
Y así es como lo uso:
fuente
new Task(() => { });
se completará al instante?Clase de ayuda simple:
Uso:
fuente
example.YourEvent
?Idealmente, no lo haces . Si bien ciertamente puede bloquear el hilo asíncrono, eso es un desperdicio de recursos, y no es ideal.
Considere el ejemplo canónico en el que el usuario va a almorzar mientras el botón espera que se haga clic.
Si ha detenido su código asincrónico mientras espera la entrada del usuario, entonces solo está desperdiciando recursos mientras ese hilo está en pausa.
Dicho esto, es mejor si en su operación asincrónica, establece el estado que necesita mantener hasta el punto donde el botón está habilitado y está "esperando" un clic. En ese punto, su
GetResults
método se detiene .Luego, cuando se hace clic en el botón , según el estado que haya almacenado, comienza otra tarea asincrónica para continuar el trabajo.
Debido a
SynchronizationContext
que se capturará en el controlador de eventos que llamaGetResults
(el compilador lo hará como resultado del uso de laawait
palabra clave que se usa y el hecho de que SynchronizationContext.Current no debe ser nulo, dado que está en una aplicación de interfaz de usuario), puede usarasync
/ meawait
gusta así:ContinueToGetResultsAsync
es el método que continúa obteniendo los resultados en caso de que se presione el botón. Si no se presiona su botón , su controlador de eventos no hace nada.fuente
GetResults
devuelve aTask
.await
simplemente dice "ejecuta la tarea, y cuando la tarea esté terminada, continúa el código después de esto". Dado que hay un contexto de sincronización, la llamada se vuelve a ordenar al hilo de la interfaz de usuario, ya que se captura en elawait
. noawait
es lo mismo queTask.Wait()
, en lo más mínimo.Wait()
. Pero el códigoGetResults()
se ejecutará en el hilo de la interfaz de usuario aquí, no hay otro hilo. En otras palabras, sí,await
básicamente ejecuta la tarea, como usted dice, pero aquí, esa tarea también se ejecuta en el hilo de la interfaz de usuario.await
el código y luego el códigoawait
, no hay bloqueo. El resto del código se vuelve a ordenar en una continuación y se programa a través deSynchronizationContext
.Stephen Toub publicó esta
AsyncManualResetEvent
clase en su blog .fuente
Con extensiones reactivas (Rx.Net)
Puede agregar Rx con Nuget Package System.
Muestra probada:
fuente
Estoy usando mi propia clase AsyncEvent para eventos pendientes.
Para declarar un evento en la clase que genera eventos:
Para plantear los eventos:
Para suscribirse a los eventos:
fuente