我有一个方法Foo.LongRunningMethod()
,它执行一些非常复杂的处理,可能会持续很长时间。一路上,它会在遇到某种情况时触发Foo.InterestingEvent
。我希望能够公开这些事件的枚举,并且我希望能够在LongRunningMethod
实际完成之前开始迭代。换句话说,我想要的是这样的:
public IEnumerable<InterestingObject> GetInterestingObjects()
{
foo.InterestingEvent += (obj) => { yield return obj; }
foo.LongRunningMethod();
yield break;
}
但这不起作用,因为明智的原因是你不能{匿名方法yield return
(因为使用yield
的方法无法返回void
,这是我们的事件处理程序)。还有另一个成语允许我完成这个吗?或者这只是一个坏主意?
答案 0 :(得分:5)
您希望能够订阅来自LongRunningMethod
的事件流,并在事件发生时从IEnumerable
产生另一个值?您可能会发现.NET Reactive Extensions非常有用:http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx
反应式扩展程序会为您提供IObservable
,这实际上只是推送IEnumerable
。您可以围绕事件(例如IObservable
)创建InterestingEvent
包装器,并从那里对其进行可枚举式处理(例如生成对象流)。
编辑:“除了采用微软的新图书馆之外,还有一个让我能够完成此任务的习惯用法吗?你正在做的是将推送序列(事件的出现)转换为拉出序列(调用
IEnumerable
)。拉动和推动可能不会协调,因此您需要在某处缓冲在拉动之前推动的新值。最直接的方法可能是采用生产者 - 消费者安排:将这些安排推送到
List<T>
调用者所使用的GetInterestingObjects
。根据事件的引发方式,您可能需要将生产者和使用者放在不同的线程上。 (当您要求它在
IObservable
和IEnumerable
之间进行转换时,所有这些都是反应式扩展程序最终会执行的操作。)
答案 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);
}
}
这保留了公共接口,同时为我提供了一个简洁的枚举,我可以将其用于需要它的场景。作为一个重要的附带好处,如果我愿意,这也允许我尽早放弃长计算,这对于基于事件或多线程的版本来说很难做到。