Saltar al contenido

Cómo ‘esperar’ generar un evento EventHandler

Hola, encontramos la solución a lo que buscas, has scroll y la encontrarás aquí.

Solución:

Editar: Esto no funciona bien para varios suscriptores, por lo que, a menos que solo tenga uno, no recomendaría usarlo.


Se siente un poco raro, pero nunca he encontrado nada mejor:

Declarar un delegado. Esto es identico a EventHandler pero devuelve una tarea en lugar de void

public delegate Task AsyncEventHandler(object sender, EventArgs e);

Luego puede ejecutar lo siguiente y siempre que el controlador declarado en los usos principales async y await correctamente, entonces esto se ejecutará de forma asíncrona:

if (SearchRequest != null) 

    Debug.WriteLine("Starting...");
    await SearchRequest(this, EventArgs.Empty);
    Debug.WriteLine("Completed");

Manejador de muestras:

 // declare handler for search request
 myViewModel.SearchRequest += async (s, e) =>
                     
     await SearchOrders();
 ;

Nota: Nunca probé esto con varios suscriptores y no estoy seguro de cómo funcionará, así que si necesita varios suscriptores, asegúrese de probarlo con cuidado.

Según la respuesta de Simon_Weaver, creé una clase de ayuda que puede manejar varios suscriptores y tiene una sintaxis similar a los eventos de C#.

public class AsyncEvent where TEventArgs : EventArgs

    private readonly List> invocationList;
    private readonly object locker;

    private AsyncEvent()
    
        invocationList = new List>();
        locker = new object();
    

    public static AsyncEvent operator +(
        AsyncEvent e, Func callback)
    
        if (callback == null) throw new NullReferenceException("callback is null");

        //Note: Thread safety issue- if two threads register to the same event (on the first time, i.e when it is null)
        //they could get a different instance, so whoever was first will be overridden.
        //A solution for that would be to switch to a public constructor and use it, but then we'll 'lose' the similar syntax to c# events             
        if (e == null) e = new AsyncEvent();

        lock (e.locker)
        
            e.invocationList.Add(callback);
        
        return e;
    

    public static AsyncEvent operator -(
        AsyncEvent e, Func callback)
    
        if (callback == null) throw new NullReferenceException("callback is null");
        if (e == null) return null;

        lock (e.locker)
        
            e.invocationList.Remove(callback);
        
        return e;
    

    public async Task InvokeAsync(object sender, TEventArgs eventArgs)
    
        List> tmpInvocationList;
        lock (locker)
        
            tmpInvocationList = new List>(invocationList);
        

        foreach (var callback in tmpInvocationList)
        
            //Assuming we want a serial invocation, for a parallel invocation we can use Task.WhenAll instead
            await callback(sender, eventArgs);
        
    

Para usarlo, lo declaras en tu clase, por ejemplo:

public AsyncEvent SearchRequest;

Para suscribir un controlador de eventos, utilizará la sintaxis familiar (igual que en la respuesta de Simon_Weaver):

myViewModel.SearchRequest += async (s, e) =>
                    
   await SearchOrders();
;

Para invocar el evento, use el mismo patrón que usamos para los eventos de C# (solo con InvokeAsync):

var eventTmp = SearchRequest;
if (eventTmp != null)

   await eventTmp.InvokeAsync(sender, eventArgs);

Si usa C # 6, uno debería poder usar el null operador condicional y escribe esto en su lugar:

await (SearchRequest?.InvokeAsync(sender, eventArgs) ?? Task.CompletedTask);

Los eventos no encajan perfectamente con async y awaitcomo has descubierto.

La forma en que se manejan las IU async eventos es diferente de lo que está tratando de hacer. La interfaz de usuario proporciona una SynchronizationContext a su async eventos, lo que les permite reanudar en el subproceso de la interfaz de usuario. Lo hace no siempre “esperarlos”.

Mejor solución (IMO)

Creo que la mejor opción es construir el tuyo propio. async-sistema pub/sub amigable, usando AsyncCountdownEvent para saber cuándo se han completado todos los controladores.

Solución menor #1

async void los métodos notifican a sus SynchronizationContext cuándo comienzan y terminan (al aumentar/disminuir el recuento de operaciones asíncronas). Toda la interfaz de usuario SynchronizationContexts ignorar estas notificaciones, pero usted pudo construya un contenedor que lo rastree y regrese cuando el conteo sea cero.

He aquí un ejemplo, usando AsyncContext de mi biblioteca AsyncEx:

SearchCommand = new RelayCommand(() => 
  IsSearching = true;
  if (SearchRequest != null) 
  
    AsyncContext.Run(() => SearchRequest(this, EventArgs.Empty));
  
  IsSearching = false;
);

Sin embargo, en este ejemplo, el subproceso de la interfaz de usuario es no bombeando mensajes mientras está en Run.

Solución menor #2

También podrías hacer el tuyo SynchronizationContext basado en un anidado Dispatcher marco que aparece cuando el recuento de operaciones asincrónicas llega a cero. Sin embargo, luego introduce problemas de reingreso; DoEvents se dejó fuera de WPF a propósito.

Si tienes alguna duda o capacidad de enriquecer nuestro post te inspiramos escribir un exégesis y con placer lo observaremos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *