"yield return" 来自事件处理程序

"yield return" from event handler

我有一个 class,它在构造函数中接受一个流。然后可以设置各种事件的回调,然后调用StartProcessing。问题是我想从一个应该 return 和 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 捕获的列表中,然后在调用 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 对象保存到列表中。

你在这里遇到的问题是你根本没有 "pull" 机制,你试图从解析器推送数据。如果解析器要将数据推送给你,而不是让调用者拉取数据,那么 GetThings 应该 return 一个 IObservable,而不是 IEnumerable,所以调用者可以在数据就绪时使用它。

如果这里有一个拉机制真的很重要,那么 Parser 不应该触发一个事件来表明它有新数据,而是调用者应该能够向它请求新数据并且让它得到它;它应该 return 所有已解析的数据,或者它本身 return 一个 IEnumerable.

有趣的问题。我想以 @servypush and pull 所说的内容为基础。在上面的实现中,您有效地将推送机制调整为拉接口。

现在,要事第一。您尚未指定对 StartProcessing() 方法的调用是否为阻塞调用。对此有几点评论:

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

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

    void ProcessAll(Action<Thing> callback);
    

另一方面,如果 StartProcessing() 方法实际上产生了一个新线程(可能更好地命名为 BeginProcessing() 并遵循 Event-based Asynchronous Pattern or another async processing pattern), you could adapt it to a pull machanism by means of a synchronization construct using a wait handle: 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 模式。