Reputation: 41378
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?
Upvotes: 2
Views: 1247
Reputation: 41378
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.
Upvotes: 1
Reputation: 54764
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 ofGetInterestingObjects
.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 anIEnumerable
.)
Upvotes: 5
Reputation: 29174
I would run LongRunningMethod()
in a separate thread communicating with the main thread: The event handler pushes the InterestingObject
s 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.
Upvotes: 2