Conditionally blocking IEnumerable<t> yield return result

1.4k views Asked by At

I'm hoping someone can help me find a more efficient solution to my problem below. I'm using the IEnumerable yield return pattern in a multi-threaded pipeline. Under most conditions this is working great, however, I have some scenarios where I need the operations in the pipeline to occur synchronously instead of in parallel threads to get the correct result. (i.e. running into concurrency issues).

Existing code is:

public override IEnumerable<Row> Execute(IEnumerable<Row> rows)
{
    foreach (Row row in rows)
    {
        //do some work....
        yield return row;
    }
}

I'm thinking of creating an optional parameter that controls blocking vs. non blocking output of the enumerator; something along the lines of:

public override IEnumerable<Row> Execute(IEnumerable<Row> rows, bool BlockingExecution)
{
    if (BlockingExecution)
    {
        return BlockingExecute(rows);
    }
    else
    {
        return NonBlockingExecute(rows);
    }
 }

 private IEnumerable<Row> NonBlockingExecute(IEnumerable<Row> rows)
 {
    foreach (Row row in rows)
    {
        //do some work....
        yield return row;
    }
 }

 private IEnumerable<Row> BlockingExecute(IEnumerable<Row> rows)
 {
     List<Row> BlockingResult = new List<Row>();
     foreach(Row r in NonBlockingExecute(rows))
     {
         BlockingResult.Add(r);
     }
     return BlockingResult;
 }

In the BlockingExecute function it seems inefficient to create a copy of the IEnumerable into a List to force the entire pipeline to flush. Is there a better way to do this?

1

There are 1 answers

2
Matthew Haugen On BEST ANSWER

In general, a "blocking execution" will be some degree of inefficient. But I'd probably use ToArray for that form, and your non-blocking version is a little confusing. Is it really supposed to do work for each one that the blocking one doesn't?

I would probably do this.

public override IEnumerable<Row> Execute(IEnumerable<Row> rows, bool BlockingExecution)
{
    if (BlockingExecution)
    {
        return rows.ToArray();
    }
    else
    {
        return NonBlockingExecute(rows); // or just "return rows". It seems like the best 
                                         // practice here, in most cases anyway
                                         // would be to move that work elsewhere
    }
 }

 private IEnumerable<Row> NonBlockingExecute(IEnumerable<Row> rows)
 {
    foreach (Row row in rows)
    {
        //do some work....
        yield return row;
    }
 }

But yes, the blocking version will require multiple iterations of the same list still (one to create the array, one to presumably read it). I'm not sure there's any way to escape that, since you want some way of loading it all into memory.