【问题标题】:Possible to turn callback calls into IEnumerable可以将回调调用转换为 IEnumerable
【发布时间】:2011-06-24 17:53:24
【问题描述】:

我正在编写一个第三方库的包装器,它有一种方法来扫描它管理的数据。该方法采用一个回调方法,它会为它找到的数据中的每个项目调用该方法。

例如方法本质上是:void Scan(Action<object> callback);

我想包装它并公开一个类似IEnumerable<object> Scan();的方法

如果不借助单独的线程来进行实际扫描和缓冲区,这是否可行?

【问题讨论】:

  • 您能否概述一下您的 3rd 方库的确切流程 - 听起来它已经产生了多个线程。还要记住,您可能有一个多播代表。另外,您如何确定扫描已完成?
  • 说实话,我看不出这样做的价值。如果你想做foreach (object obj in scannable.Scan()) { /* do something with obj */ },那真的比scannable.Scan(obj => { /* do something with obj */ });好吗?
  • @weismat:不,它没有产生线程,它基本上在内部迭代它的数据存储,并为每条记录调用回调。
  • @Flynn1179:是的,这是可行的,但我正在编写一个包装器供其他人使用,所以希望它尽可能明显......

标签: c# callback enumerable


【解决方案1】:

看看yield 关键字——它允许您拥有一个看起来像IEnumerable 但实际上对每个返回值进行处理的方法。

【讨论】:

  • 是的,但我认为 OP 想知道如何将这种方法与 Action<object> 回调挂钩。
  • 哦,我明白你的意思了——更难的问题!
  • 是的,我最初的想法是单行方法Scan(x => { yield return x; });,但不幸的是yield不是那样工作的。
【解决方案2】:

这个怎么样:

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

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

    Scan(action);

    return objList;
}

【讨论】:

  • Scan(action) 可能会添加项目,而返回的IEnumerable 将被迭代,从而导致异常,因为回调将执行异步。
  • 是的,这行得通,但潜在的回调数量可能达到数百万,所以我宁愿在迭代之前不要缓冲全部。
【解决方案3】:

您应该调查Rx project — 这允许将事件源作为IEnumerable 使用。

我不确定它是否允许像这样呈现普通回调(它针对 .NET 事件),但值得一看,因为它应该可以将常规回调呈现为 IObservable

【讨论】:

    【解决方案4】:

    您可以使用 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);
            }
        }
    }
    

    请记住,这不会处理取消之类的事情,因为回调方法实际上不允许这样做。一个合适的解决方案需要外部库的部分工作。

    【讨论】:

    • 嗯,那会很好用。我希望避免必须使用单独的线程,但似乎只使用一个线程是不可能的。这似乎是最简单的方法。谢谢。
    • 是的,我认为实际上不可能仅使用一个线程将其放入 Enumerable 中。我能得到的最接近的是 Observable,但在这种情况下,它只是另一个名称的回调 :)
    【解决方案5】:

    这里是一个阻塞枚举器(Scan方法需要在单独的线程中运行)

        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&lt;Object&gt;,但我不推荐它。 IEnumerable 列表意味着您可以在同一个列表上运行多个枚举器,在这种情况下您不能这样做。

    【讨论】:

      猜你喜欢
      • 2012-03-15
      • 2015-10-16
      • 1970-01-01
      • 2018-04-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-01
      • 2019-04-27
      相关资源
      最近更新 更多