Можно превратить обратные вызовы в IEnumerable - PullRequest
8 голосов
/ 10 февраля 2011

Я пишу обертку вокруг сторонней библиотеки, и у нее есть метод для сканирования данных, которыми она управляет. Метод принимает метод обратного вызова, который он вызывает для каждого элемента данных, которые он находит.

например. Метод по сути: void Scan(Action<object> callback);

Я хочу обернуть его и раскрыть метод, подобный IEnumerable<object> Scan();

Возможно ли это, не прибегая к отдельному потоку для фактического сканирования и буфера?

Ответы [ 5 ]

4 голосов
/ 10 февраля 2011

Вы можете сделать это довольно просто с помощью 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);
        }
    }
}

Помните, что это не заботится о таких вещах, как отмена, поскольку метод обратного вызова не позволяет этого. Правильное решение потребует работы со стороны внешней библиотеки.

4 голосов
/ 10 февраля 2011

Вы должны исследовать проект Rx - это позволяет использовать источник события как IEnumerable.

Я не уверен, разрешает ли он представлять ванильные обратные вызовы как таковые (они предназначены для событий .NET), но стоило бы посмотреть, так как можно было бы представить обычный обратный вызов как IObservable.

2 голосов
/ 10 февраля 2011

Вот перечислитель блокировки (метод сканирования должен запускаться в отдельном потоке)

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

Вы могли бы обернуть это в простой IEnumerable<Object>, но я бы не рекомендовал это. IEnumerable списки подразумевают, что вы можете запускать несколько перечислителей в одном списке, чего вы не можете в этом случае.

0 голосов
/ 10 февраля 2011

Как насчет этого:

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

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

    Scan(action);

    return objList;
}
0 голосов
/ 10 февраля 2011

Взгляните на ключевое слово yield , которое позволит вам иметь метод, похожий на IEnumerable, но фактически обрабатывающий каждое возвращаемое значение.

...