How to yield return inside anonymous methods?

后端 未结 7 525
南笙
南笙 2021-01-07 16:43

Basically I have an anonymous method that I use for my BackgroundWorker:

worker.DoWork += ( sender, e ) =>
{
    foreach ( var effect in Glob         


        
7条回答
  •  自闭症患者
    2021-01-07 17:04

    Unless I'm missing something, you can't do what you're asking.

    (I do have an answer for you, so please read past my explanation of why you can't do what you're doing first, and then read on.)

    You full method would look something like this:

    public static IEnumerable GetSomeValues()
    {
        // code to set up worker etc
        worker.DoWork += ( sender, e ) =>
        {
            foreach ( var effect in GlobalGraph.Effects )
            {
                // Returns EffectResult
                yield return image.Apply (effect);
            }
        };
    }
    

    If we assume that your code was "legal" then when GetSomeValues is called, even though the DoWork handler is added to worker, the lambda expression isn't executed until the DoWork event is fired. So the call to GetSomeValues completes without returning any results and the lamdba may or may not get called at a later stage - which is then too late for the caller of the GetSomeValues method anyway.

    Your best answer is to the use Rx.

    Rx turns IEnumerable on its head. Instead of requesting values from an enumerable, Rx has values pushed to you from an IObservable.

    Since you're using a background worker and responding to an event you are effectively having the values pushed to you already. With Rx it becomes easy to do what you're trying to do.

    You have a couple of options. Probably the simplest is to do this:

    public static IObservable> GetSomeValues()
    {
        // code to set up worker etc
        return from e in Observable.FromEvent(worker, "DoWork")
               select (
                   from effect in GlobalGraph.Effects
                   select image.Apply(effect)
               );
    }
    

    Now callers of your GetSomeValues method would do this:

    GetSomeValues().Subscribe(ers =>
    {
        foreach (var er in ers)
        {
            // process each er
        }
    });
    

    If you know that DoWork is only going to fire once, then this approach might be a little better:

    public static IObservable GetSomeValues()
    {
        // code to set up worker etc
        return Observable
            .FromEvent(worker, "DoWork")
            .Take(1)
            .Select(effect => from effect in GlobalGraph.Effects.ToObservable()
                              select image.Apply(effect))
            .Switch();  
    }
    

    This code looks a little more complicated, but it just turns a single do work event into a stream of EffectResult objects.

    Then the calling code looks like this:

    GetSomeValues().Subscribe(er =>
    {
        // process each er
    });
    

    Rx can even be used to replace the background worker. This might be the best option for you:

    public static IObservable GetSomeValues()
    {
        // set up code etc
        return Observable
            .Start(() => from effect in GlobalGraph.Effects.ToObservable()
                         select image.Apply(effect), Scheduler.ThreadPool)
            .Switch();  
    }
    

    The calling code is the same as the previous example. The Scheduler.ThreadPool tells Rx how to "schedule" the processing of subscriptions to the observer.

    I hope this helps.

提交回复
热议问题