2011-02-10 11 views
8

Piszę wrapper wokół biblioteki 3rd party, i ma metodę skanowania danych, którymi zarządza. Metoda pobiera metodę wywołania zwrotnego, którą wywołuje dla każdego elementu w znalezionych danych.Możliwość przełączania wywołań zwrotnych na IEnumerable

np. Metoda ta jest w istocie: void Scan(Action<object> callback);

Chcę owinąć go i narazić metoda jak IEnumerable<object> Scan();

Czy jest to możliwe bez uciekania się do osobnego wątku zrobić rzeczywiste skanowanie i bufor?

+0

Czy możesz określić dokładny przepływ w bibliotece innej firmy - brzmi to tak, jakby już się pojawił w wielu wątkach. Pamiętaj też, że możesz mieć delegatów multiemisji. Również, w jaki sposób określasz, że skanowanie zostało zakończone? – weismat

+0

Szczerze mówiąc, nie widzę w tym żadnej wartości. Jeśli chcesz zrobić foreach (object obj w scannable.Scan()) {/ * zrób coś z obj * /} ', to jest to naprawdę lepsze niż' scannable.Scan (obj => {/ * zrób coś z obj * /}); '? – Flynn1179

+0

@weismat: nie, nie odradza się wątków, w zasadzie iteruje wewnętrznie do magazynu danych i wywołuje wywołanie zwrotne dla każdego rekordu. – Gareth

Odpowiedz

4

Można to zrobić po prostu z reaktywnymi:

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

Pamiętaj, że to nie dbać o takie rzeczy jak odwołania, ponieważ metoda zwrotna naprawdę nie pozwala na to. Właściwe rozwiązanie wymagałoby pracy ze strony biblioteki zewnętrznej.

+0

Hmmm, to będzie ładnie działać.Miałem nadzieję, że nie będę musiał używać oddzielnego wątku, ale nie wydaje się to możliwe przy użyciu tylko jednego wątku. Wydaje się, że jest to najprostszy sposób. Dzięki. – Gareth

+0

Tak, nie sądzę, że jest to możliwe, aby uzyskać go do zmiennej przy użyciu tylko jednego wątku. Najbliższe, jakie mogłem uzyskać, było Obserwowalne, ale w takim przypadku jest to tylko wywołanie zwrotne pod inną nazwą :) – porges

-1

Spójrz na słowo kluczowe yield - które pozwoli ci uzyskać metodę, która wygląda jak IEnumerable, ale która faktycznie przetwarza dla każdej wartości zwracanej.

+1

Tak, ale myślę, że OP chce wiedzieć, jak to złapać metoda aż do wywołania zwrotnego 'Action '. – LukeH

+0

Och, rozumiem, co masz na myśli - o wiele trudniejsze pytanie! –

+0

Tak, moją pierwszą myślą była jednolinijkowa metoda 'Scan (x => {yield return x;});', ale niestety wydajność nie działa w ten sposób. – Flynn1179

0

Jak o tym jednym:

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

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

    Scan(action); 

    return objList; 
} 
+1

'Scan (akcja)' może dodawać elementy, podczas gdy zwracany 'IEnumerable' będzie iterowany, powodując wyjątek, ponieważ wywołanie zwrotne wykona asynchronizację. – Cornelius

+0

Tak, to działa, ale potencjalna liczba wywołań zwrotnych może znajdować się w milionach, więc wolałbym nie buforować całej zawartości przed jej iterowaniem. – Gareth

4

Należy zbadać Rx project - pozwala to źródło zdarzenia mają być spożywane jako IEnumerable.

Nie jestem pewien, czy pozwala on na prezentację callbillów wanilii (jest skierowany na zdarzenia .NET), ale warto byłoby sprawdzić, jak powinno być możliwe regularne oddzwanianie jako IObservable.

2

Oto blokowanie wyliczający (metoda skanowania musi uruchomić w osobnym wątku)

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

Można owinąć go w prosty IEnumerable<Object>, ale nie polecam go. IEnumerable list oznacza, że ​​możesz uruchomić wiele modułów wyliczających na tej samej liście, czego nie możesz w tym przypadku.

Powiązane problemy