8

I'm writing a wrapper around a 3rd party library, and it has a method to scan the data it manages. The method takes a callback method that it calls for each item in the data that it finds.

e.g. The method is essentially: void Scan(Action<object> callback);

I want to wrap it and expose a method like IEnumerable<object> Scan();

Is this possible without resorting to a separate thread to do the actual scan and a buffer?

Gareth
  • 2,424
  • 5
  • 26
  • 44
  • Could you outline the exact flow on your 3rd party library - it sounds like it is already spawning to multiple threads. Also keep in mind that you may have a Multicast delegates. Also how are you determining that the scan is finished? – weismat Feb 10 '11 at 10:19
  • To be honest, I can't see the value in doing this. If you want to do `foreach (object obj in scannable.Scan()) { /* do something with obj */ }`, is that really any better than `scannable.Scan(obj => { /* do something with obj */ });`? – Flynn1179 Feb 10 '11 at 10:47
  • @weismat: no, its not spawning threads, it basically iterates against it's data store internally, and calls the callback for each record. – Gareth Feb 10 '11 at 10:55
  • @Flynn1179: Yes, that is doable, but I'm writing a wrapper for other people to use, so want it to be as obvious as possible... – Gareth Feb 10 '11 at 10:56

5 Answers5

5

You can do this quite simply with Reactive:

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

Remember that this doesn't take care of things like cancellation, since the callback method doesn't really allow it. A proper solution would require work on the part of the external library.

porges
  • 30,133
  • 4
  • 83
  • 114
  • Hmmm, that will work nicely. I hoped to avoid have to use a separate thread, but it doesn't seem possible using only one thread. This seems the simplest way to do it. Thanks. – Gareth Feb 10 '11 at 11:16
  • Yeah I don't think it's actually possible to get it into an Enumerable using only one thread. The closest I could get was an Observable, but in that case it's just a callback by another name :) – porges Feb 10 '11 at 11:24
4

You should investigate the Rx project — this allows an event source to be consumed as an IEnumerable.

I'm not sure if it allows vanilla callbacks to be presented as such (it's aimed at .NET events) but it would be worth a look as it should be possible to present a regular callback as an IObservable.

Paul Ruane
  • 37,459
  • 12
  • 63
  • 82
2

Here is a blocking enumerator (the Scan method needs to run in a separate thread)

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

You could wrap it in a simple IEnumerable<Object>, but I would not recommend it. IEnumerable lists implies that you can run multiple enumerators on the same list, which you can't in this case.

jgauffin
  • 99,844
  • 45
  • 235
  • 372
0

How about this one:

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

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

    Scan(action);

    return objList;
}
decyclone
  • 30,394
  • 6
  • 63
  • 80
  • 1
    The `Scan(action)` might add items while the returned `IEnumerable` would be iterated resulting in an exception since the callback will execute async. – Cornelius Feb 10 '11 at 10:15
  • Yea, that works, but the potential number of callbacks can be in the millions, so I'd rather not buffer the whole lot up before iterating over it. – Gareth Feb 10 '11 at 10:43
-1

Take a look at the yield keyword -- which will allow you to have a method that looks like an IEnumerable but which actually does processing for each return value.

Dr Herbie
  • 3,930
  • 1
  • 25
  • 28