Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Parallel.ForEach start threads if the source is empty?

I am using a Parallel.ForEach in this way:

public void myMethod(IEnumerable<MyType> paramIeCollection)
{
    Parallel.Foreach(paramIeCollection,
          (iterator) =>
          {
              //Do something
          });
}

I am wondering if when paramIeCollection is empty, the Parallel.ForEach starts anyway and take threads from Thread Pool and consumes resources or if it first checks if there are items in the collection.

If it doesn't check, to avoid that, I am thinking in this code:

if(paramIeCollection.count > 0)
{
    //run Parallel.Foreach
}

So the question is, is it a good practice to check if the collection has items before calling Parallel.ForEach or if it isn't needed?

like image 587
Álvaro García Avatar asked Feb 12 '18 12:02

Álvaro García


1 Answers

The truth be known it does check. However if you go through the source there are a handful of other checks and balances it does before it figures it out.

If you have processor instruction OCD, a simple check beforehand like if(list.count > 0) will probably save you a bunch of IL. however in the real world it wont make much of a difference

System.Threading.Tasks Reference Source

For instance, for the simple IEnumerable overload, you can follow the source code through E.g

public static ParallelLoopResult ForEach<TSource>(IEnumerable<TSource> source, Action<TSource> body)

Calls ForEachWorker

private static ParallelLoopResult ForEachWorker<TSource, TLocal>(
        IEnumerable<TSource> source,
        ParallelOptions parallelOptions,
        Action<TSource> body,
        Action<TSource, ParallelLoopState> bodyWithState,
        Action<TSource, ParallelLoopState, long> bodyWithStateAndIndex,
        Func<TSource, ParallelLoopState, TLocal, TLocal> bodyWithStateAndLocal,
        Func<TSource, ParallelLoopState, long, TLocal, TLocal> bodyWithEverything,
        Func<TLocal> localInit, Action<TLocal> localFinally)

Calls inline

// This is an honest-to-goodness IEnumerable.  Wrap it in a Partitioner and defer to our
        // ForEach(Partitioner) logic.
return PartitionerForEachWorker<TSource, TLocal>(Partitioner.Create(source), parallelOptions, body, bodyWithState,
            bodyWithStateAndIndex, bodyWithStateAndLocal, bodyWithEverything, localInit, localFinally);

PartitionerForEachWorker

// Main worker method for Parallel.ForEach() calls w/ Partitioners.
private static ParallelLoopResult PartitionerForEachWorker<TSource, TLocal>(
        Partitioner<TSource> source, // Might be OrderablePartitioner
        ParallelOptions parallelOptions,
        Action<TSource> simpleBody,
        Action<TSource, ParallelLoopState> bodyWithState,
        Action<TSource, ParallelLoopState, long> bodyWithStateAndIndex,
        Func<TSource, ParallelLoopState, TLocal, TLocal> bodyWithStateAndLocal,
        Func<TSource, ParallelLoopState, long, TLocal, TLocal> bodyWithEverything,
        Func<TLocal> localInit,
        Action<TLocal> localFinally)

Which eventually does the check

while (myPartition.MoveNext())
like image 71
TheGeneral Avatar answered Nov 14 '22 22:11

TheGeneral