来自事件处理程序的“yield return”

时间:2016-07-07 20:25:00

标签: c# lambda ienumerable

我有一个在构造函数中获取流的类。然后,您可以为各种事件设置回调,然后调用StartProcessing。问题是我想从一个应该返回IEnumerable

的函数中使用它

示例:

public class Parser
{
    public Parser(System.IO.Stream s) { // saves stream and does some set up }
    public delegate void OnParsedHandler(List<string> token);
    public event OnParsedHandler OnParsedData;
    public void StartProcessing()
    {
        // reads stream and makes callback when it has a whole record
    }
 }

 public class Application
 {
      public IEnumerable<Thing> GetThings(System.IO.Stream s)
      {
            Parser p = new Parser(s);
            p.OnParsedData += (List<string> str) => 
            {
                Thing t = new Thing(str[0]);
                // here is where I would like to yield
                // but I can't
                yield return t;
            };


            p.StartProcessing();
      }
 }

现在我的解决方案,不是那么好,是将所有的东西放入由lambda捕获的List中,然后在调用StartProcessing后迭代它们。

public class Application
 {
      public IEnumerable<Thing> GetThings(System.IO.Stream s)
      {
            Parser p = new Parser(s);
            List<Thing> thingList = new List<Thing>();

            p.OnParsedData += (List<string> str) => 
            {
                Thing t = new Thing(str[0]);
                thingList .Add(t);
            };


            p.StartProcessing();
            foreach(Thing t in thingList )
            {
                  yield return t;
            }
      }
 }

这里的问题是现在我必须将所有Thing个对象保存到列表中。

2 个答案:

答案 0 :(得分:3)

你遇到的问题是你在这里没有从根本上有一个“拉”机制,你试图从解析器推送数据。如果解析器要向您推送数据,而不是让调用者提取数据,那么GetThings应该返回IObservable而不是IEnumerable,这样调用者就可以使用数据准备好了。

如果在这里拥有拉动机制非常重要,那么Parser不应该触发事件来指示它有新数据,而是调用者应该能够向它询问新数据并拥有它得到它;它应该返回所有已解析的数据,或者本身返回IEnumerable

答案 1 :(得分:1)

有趣的问题。我想基于 @servy push and pull所说的内容。在上面的实现中,您正在有效地将推送机制适应拉动界面。

现在,首先要做的事情。您尚未指定对StartProcessing()方法的调用是否为阻止调用。关于这一点的几点评论:

  • 如果方法是阻塞的(同步),那么无论如何都没有必要使它适应拉模型。调用者将看到在单个阻止调用中处理的所有数据。

  • 在这方面,通过事件处理程序间接接收数据会分散到两个看似无关的构造中,否则应该是单个的,有凝聚力的显式操作。例如:

    void ProcessAll(Action<Thing> callback);
    

另一方面,如果StartProcessing()方法实际产生一个新线程(可能更好地命名为BeginProcessing()并遵循Event-based Asynchronous Pattern或另一个异步处理模式),您可以将其调整为通过使用等待句柄的同步构造的拉动机制:ManualResetEvent,互斥体等。伪代码:

public IEnumerable<Thing> GetThings(System.IO.Stream s)
{
    var parser = new Parser(s);
    var waitable = new AutoResetEvent(false);
    Thing item = null;

    parser.OnParsedData += (Thing thing) => 
    {
        item = thing;
        waitable.Set();
    };

    IAsyncResult result = parser.BeginProcessing();
    while (!result.IsCompleted)
    {
        waitable.WaitOne();
        yield return item;            
    }
}

声明

上述代码仅用作表达想法的手段。它不是线程安全的,并且同步机制无法正常工作。有关详细信息,请参阅producer-consumer模式。