2011-02-10 5 views
8

Sto scrivendo un wrapper su una libreria di terze parti e ha un metodo per scansionare i dati che gestisce. Il metodo accetta un metodo di richiamata che richiama ogni elemento nei dati che trova.È possibile attivare le chiamate di richiamata in IEnumerable

ad es. Il metodo è essenzialmente: void Scan(Action<object> callback);

voglio avvolgerla ed esporre un metodo come IEnumerable<object> Scan();

Questo è possibile senza ricorrere a un thread separato per fare la scansione reale e un buffer?

+0

Puoi delineare il flusso esatto nella libreria di terze parti: sembra che stia già generando più thread. Inoltre, tieni presente che potresti avere un delegato Multicast. Inoltre, come stai determinando che la scansione è finita? – weismat

+0

Per essere onesti, non riesco a vedere il valore nel fare ciò. Se vuoi fare 'foreach (object obj in scannable.Scan()) {/ * fai qualcosa con obj * /}', è davvero meglio di 'scannable.Scan (obj => {/ * fai qualcosa con obj * /}); '? – Flynn1179

+0

@weismat: no, i suoi thread non generano spawning, in pratica itera contro il suo data store internamente e chiama il callback per ogni record. – Gareth

risposta

4

È possibile farlo semplicemente con reattiva:

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); 
     } 
    } 
} 

ricordare che questo non si prende cura di cose come la cancellazione, dal momento che il metodo di callback in realtà non lo permette. Una soluzione adeguata richiederebbe un lavoro sulla parte della libreria esterna.

+0

Hmmm, funzionerà bene.Speravo di evitare di usare un thread separato, ma non sembra possibile usando solo un thread. Questo sembra il modo più semplice per farlo. Grazie. – Gareth

+0

Sì, non penso che sia effettivamente possibile farlo entrare in un Enumerable usando solo un thread. Il più vicino che ho potuto ottenere era un Observable, ma in questo caso è solo un callback con un altro nome :) – porges

-1

Dai un'occhiata alla parola chiave yield, che ti consente di avere un metodo simile a IEnumerable ma che in realtà esegue l'elaborazione per ogni valore restituito.

+1

Sì, ma penso che l'OP voglia sapere come agganciare tale un metodo fino ad un 'call ' callback. – LukeH

+0

Oh, capisco cosa intendi: domanda molto più difficile! –

+0

Sì, il mio pensiero iniziale era un metodo a una riga 'Scan (x => {yield return x;});', ma sfortunatamente yield non funziona così. – Flynn1179

0

Che ne dite di questo:

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

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

    Scan(action); 

    return objList; 
} 
+1

'Scan (action)' potrebbe aggiungere elementi mentre il '' IEnumerable' restituito verrebbe ripetuto causando un'eccezione poiché la callback verrebbe eseguita asincrona. – Cornelius

+0

Sì, funziona, ma il numero potenziale di callback può essere in milioni, quindi preferisco non bufferizzare l'intero lotto prima di iterarlo. – Gareth

4

Si dovrebbe indagare il Rx project - questo permette un'origine evento da consumare come IEnumerable.

Non sono sicuro se consente di richiamare i richiami di vanilla come tali (è destinato agli eventi .NET) ma varrebbe la pena dare un'occhiata in quanto dovrebbe essere possibile presentare una normale callback come IObservable.

2

Ecco un enumeratore di blocco (il metodo di scansione deve essere eseguito in un thread separato)

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); 
     } 
    } 

Si poteva avvolgerlo in un semplice IEnumerable<Object>, ma io non lo consiglio. Gli elenchi IEnumerable implicano che è possibile eseguire più enumeratori sullo stesso elenco, cosa che in questo caso non è possibile.