2011-02-10 9 views
8

Estoy escribiendo un contenedor en una biblioteca de terceros, y tiene un método para escanear los datos que administra. El método toma un método de devolución de llamada que llama para cada elemento en los datos que encuentra.Posible convertir llamadas de devolución de llamada en IEnumerable

p. Ej. El método es esencialmente: void Scan(Action<object> callback);

Quiero envolverlo y exponer un método como IEnumerable<object> Scan();

Es esto posible sin recurrir a un hilo separado para hacer la exploración real y un tampón?

+0

¿Podría describir el flujo exacto en su biblioteca de terceros? Parece que ya está generando múltiples hilos. También tenga en cuenta que puede tener un delegado de multidifusión. Además, ¿cómo determina que el escaneo haya finalizado? – weismat

+0

Para ser honesto, no puedo ver el valor de hacer esto. Si quieres hacer 'foreach (objeto obj en scannable.Scan()) {/ * hacer algo con obj * /}', es realmente mejor que 'scannable.Scan (obj => {/ * hacer algo con obj * /}); '? – Flynn1179

+0

@weismat: no, no son hilos de desove, básicamente itera contra su almacén de datos internamente y llama a la devolución de llamada para cada registro. – Gareth

Respuesta

4

Esto se puede hacer simplemente con Reactivo:

class Program 
{ 
    static void Main(string[] args) 
    { 
     foreach (var x in CallBackToEnumerable<int>(Scan)) 
      Console.WriteLine(x); 
    } 

    static IEnumerable<T> CallBackToEnumerable<T>(Action<Action<T>> functionReceivingCallback) 
    { 
     return Observable.Create<T>(o => 
     { 
      // Schedule this onto another thread, otherwise it will block: 
      Scheduler.Later.Schedule(() => 
      { 
       functionReceivingCallback(o.OnNext); 
       o.OnCompleted(); 
      }); 

      return() => { }; 
     }).ToEnumerable(); 
    } 

    public static void Scan(Action<int> act) 
    { 
     for (int i = 0; i < 100; i++) 
     { 
      // Delay to prove this is working asynchronously. 
      Thread.Sleep(100); 
      act(i); 
     } 
    } 
} 

recordar que esto no se ocupa de cosas como la cancelación, ya que el método de devolución de llamada en realidad no lo permite. Una solución adecuada requeriría trabajo por parte de la biblioteca externa.

+0

Hmmm, eso funcionará muy bien.Esperaba evitar tener que usar un hilo por separado, pero no parece posible usar solo un hilo. Esta parece ser la forma más simple de hacerlo. Gracias. – Gareth

+0

Sí, no creo que sea realmente posible meterlo en un Enumerable usando solo un hilo. Lo más cerca que pude encontrar fue un Observable, pero en ese caso es solo una devolución de llamada con otro nombre :) – porges

-1

Eche un vistazo a la palabra clave yield, que le permitirá tener un método que se parece a un IEnumerable pero que realmente procesa para cada valor de retorno.

+1

Sí, pero creo que el OP quiere saber cómo engancharlos un método hasta una devolución de llamada 'Action '. – LukeH

+0

Oh, ya veo a qué te refieres, ¡una pregunta mucho más difícil! –

+0

Sí, mi pensamiento inicial fue un método de una sola línea 'Scan (x => {yield return x;});', pero desafortunadamente el rendimiento no funciona así. – Flynn1179

0

¿Qué tal este:

IEnumerable<Object> Scan() 
{ 
    List<Object> objList = new List<Object>(); 

    Action<Object> action = (obj) => { objList.Add(obj); }; 

    Scan(action); 

    return objList; 
} 
+1

El 'Escaneo (acción)' podría agregar elementos mientras que el 'IEnumerable' devuelto se iteraría dando como resultado una excepción ya que la retrollamada se ejecutará como sincronización. – Cornelius

+0

Sí, eso funciona, pero el número potencial de devoluciones de llamada puede ser de millones, así que prefiero no almacenar todo el lote antes de iterar sobre él. – Gareth

4

Debe investigar la Rx project - esto permite que una fuente de eventos para ser consumido como una IEnumerable.

No estoy seguro de si permite la presentación de devoluciones de llamada vainilla como tal (está dirigido a eventos .NET) pero valdría la pena, ya que debería ser posible presentar una devolución de llamada normal como IObservable.

2

Aquí es un empadronador bloqueo (el método de exploración tiene que ejecutar en un hilo separado)

public class MyEnumerator : IEnumerator<object> 
    { 
     private readonly Queue<object> _queue = new Queue<object>(); 
     private ManualResetEvent _event = new ManualResetEvent(false); 

     public void Callback(object value) 
     { 
      lock (_queue) 
      { 
       _queue.Enqueue(value); 
       _event.Set(); 
      } 
     } 

     public void Dispose() 
     { 

     } 

     public bool MoveNext() 
     { 
      _event.WaitOne(); 
      lock (_queue) 
      { 
       Current = _queue.Dequeue(); 
       if (_queue.Count == 0) 
        _event.Reset(); 
      } 
      return true; 
     } 

     public void Reset() 
     { 
      _queue.Clear(); 
     } 

     public object Current { get; private set; } 

     object IEnumerator.Current 
     { 
      get { return Current; } 
     } 
    } 

    static void Main(string[] args) 
    { 
     var enumerator = new MyEnumerator(); 
     Scan(enumerator.Callback); 

     while (enumerator.MoveNext()) 
     { 
      Console.WriteLine(enumerator.Current); 
     } 
    } 

Usted podría envolverlo en un simple IEnumerable<Object>, pero yo no lo recomendaría. IEnumerable listas implica que puede ejecutar múltiples enumeradores en la misma lista, que no puede en este caso.

Cuestiones relacionadas