使用事件处理程序的yield

时间:2010-07-15 21:54:53

标签: c# events yield

我有一个方法Foo.LongRunningMethod(),它执行一些非常复杂的处理,可能会持续很长时间。一路上,它会在遇到某种情况时触发Foo.InterestingEvent。我希望能够公开这些事件的枚举,并且我希望能够在LongRunningMethod实际完成之前开始迭代。换句话说,我想要的是这样的:

public IEnumerable<InterestingObject> GetInterestingObjects()
{
    foo.InterestingEvent += (obj) => { yield return obj; }
    foo.LongRunningMethod();

    yield break;
}

但这不起作用,因为明智的原因是你不能{匿名方法yield return(因为使用yield的方法无法返回void,这是我们的事件处理程序)。还有另一个成语允许我完成这个吗?或者这只是一个坏主意?

3 个答案:

答案 0 :(得分:5)

您希望能够订阅来自LongRunningMethod的事件流,并在事件发生时从IEnumerable产生另一个值?您可能会发现.NET Reactive Extensions非常有用:http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx

反应式扩展程序会为您提供IObservable,这实际上只是推送IEnumerable。您可以围绕事件(例如IObservable)创建InterestingEvent包装器,并从那里对其进行可枚举式处理(例如生成对象流)。

  

编辑:“除了采用微软的新图书馆之外,还有一个让我能够完成此任务的习惯用法吗?你正在做的是将推送序列(事件的出现)转换为拉出序列(调用IEnumerable)。

     

拉动和推动可能不会协调,因此您需要在某处缓冲在拉动之前推动的新值。最直接的方法可能是采用生产者 - 消费者安排:将这些安排推送到List<T>调用者所使用的GetInterestingObjects

     

根据事件的引发方式,您可能需要将生产者和使用者放在不同的线程上。 (当您要求它在IObservableIEnumerable之间进行转换时,所有这些都是反应式扩展程序最终会执行的操作。)

答案 1 :(得分:2)

我将在与主线程通信的单独线程中运行LongRunningMethod():事件处理程序将InterestingObject推送到某个同步队列,并在新值到达时向主线程发出信号。

主线程等待队列中的对象并使用yield返回它们以返回它们。

或者你也可以在每次引发事件时阻止子线程,直到主线程返回了值并且IEnumerable的使用者请求下一个值。

答案 2 :(得分:1)

Tim Robinson的回答引用了我的想法:

  

您正在做的是将推送序列(事件的发生)转换为拉动序列(调用IEnumerable)。

将推送序列转换为拉动序列很困难,这是我问题的根源。但相反(将拉动序列转换为推动序列)是微不足道的,这种洞察力给了我解决方案。我将LongRunningMethod更改为内部可枚举版本,并使用yield return替换每个事件回调并在结尾添加yield break进行简单的重构。然后我将现有的LongRunningMethod变成了一个包装器,它只是为返回的所有内容触发事件:

internal IEnumerable<InterestingObject> FindInterestingObjects() 
{ 
    /* etc */ 
}

public void LongRunningMethod()
{
    foreach (var obj in FindInterestingObjects())
    {
        OnInterestingEvent(obj);
    }
}

这保留了公共接口,同时为我提供了一个简洁的枚举,我可以将其用于需要它的场景。作为一个重要的附带好处,如果我愿意,这也允许我尽早放弃长计算,这对于基于事件或多线程的版本来说很难做到。