tags:

views:

58

answers:

3

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?

+5  A: 

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.)

Tim Robinson
+2  A: 

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.

MartinStettner
+1  A: 

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.

JSBangs