使用事件处理程序中的yield

发布于 2024-09-09 18:49:37 字数 541 浏览 5 评论 0原文

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

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

    yield break;
}

不过,这不起作用,因为你不能从匿名方法中产生返回(并且因为使用 的方法>yield 不能返回 void,而我们的事件处理程序会这样做)。还有另一种习惯用法可以让我完成这个任务吗?或者这只是一个坏主意?

I have a method Foo.LongRunningMethod(), which does some very complicated processing that may go on for a long time. Along the way, it fires Foo.InterestingEvent whenever it encounters a certain condition. I'd like to be able to expose an enumeration of those events, and I'd like to be able to start iterating before LongRunningMethod actually finishes. In other words, what I want is something like this:

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

    yield break;
}

This doesn't work, though, for the sensible reason that you can't yield return from an anonymous method (and because a method using yield cannot return void, which our event handler does). Is there another idiom that allows me to accomplish this? Or is this just a bad idea?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

睫毛上残留的泪 2024-09-16 18:49:37

您希望能够订阅来自 LongRunningMethod 的事件流,并在事件发生时从 IEnumerable 生成另一个值?您可能会发现 .NET 反应式扩展很有用:http://msdn.microsoft。 com/en-us/devlabs/ee794896.aspx

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

编辑:“除了采用 Microsoft 的新库之外,还有什么方法可以让我完成此任务吗?”您正在做的是将推送序列(事件的发生)转换为拉动序列(调用 IEnumerable)。

拉取和推送可能不会协调,因此您需要在某个地方缓冲拉取之前推送的新值。最直接的方法可能是采用生产者-消费者安排:将它们推送到由 GetInterestingObjects 的调用方使用的 List 中。

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

You want to be able to subscribe to a stream of events that come from LongRunningMethod and, when an event occurs, yield another value from an IEnumerable? You might find the .NET Reactive Extensions useful: http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx

The reactive extensions give you IObservable, which is in effect a push-only IEnumerable. You can create an IObservable wrapper around an event (such as your InterestingEvent) and do enumerable-style processing on it from there (such as yielding a stream of objects).

Edit: "is there an idiom that allows me to accomplish this" other than adopting a new library from Microsoft? What you're doing is turning a push sequence (occurrences of an event) into a pull one (calls into IEnumerable).

The pulls and pushes probably aren't going to be coordinated, so you'll need somewhere to buffer new values that were pushed before a pull was made. The most straightforward way might be to adopt a producer-consumer arrangement: push those into a List<T> that's consumed by the caller of GetInterestingObjects.

Depending on how the events are raised, you might need to put the producer and consumer on separate threads. (All of this is what the reactive extensions end up doing, when you ask it to convert between an IObservable and an IEnumerable.)

小忆控 2024-09-16 18:49:37

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

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

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

I would run LongRunningMethod() in a separate thread communicating with the main thread: The event handler pushes the InterestingObjects into some synchronized queue and signals the main thread, when a new value arrives.

The main thread waits for objects in the queue and returns them using yield to return them.

Alternatively you could also block the subthread every time an event is raised until the main thread has returned the value and the consumer of theIEnumerable requests the next value.

笑着哭最痛 2024-09-16 18:49:37

蒂姆·罗宾逊的回答中的这句话让我思考:

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

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

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

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

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

This quote from Tim Robinson's answer got me thinking:

What you're doing is turning a push sequence (occurrences of an event) into a pull one (calls into IEnumerable).

Turning a push sequence into a pull sequence is difficult, and the root of my problems here. But the reverse (turning a pull sequence into a push sequence) is trivial, and this insight gave me the solution. I changed LongRunningMethod into an internal enumerable version, with the trivial refactoring of replacing every event callback with yield return and adding a yield break at the end. Then I turned the existing LongRunningMethod into a wrapper that just fires the event for everything returned:

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

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

This preserves the public interface, while giving me a neat enumeration that I can use for the scenarios that require it. As a significant side-benefit, this also allows me to abandon the long computation early if I want to, something that would be difficult to do with event-based or multi-threaded versions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文