2

Consider my following (simplified) code:

public double ComputeSum(List<double> numbers, ref double threshold, Object thresholdLock)
{
    double sum = 0;
    Object sumLock = new Object();

    Parallel.ForEach (numbers, (number) => 
    {
        bool numberIsGreaterOrEqualThanThreshold;
        lock (thresholdLock)
        {
            numberIsGreaterOrEqualThanThreshold = number >= threshold;
        }
        if (numberIsGreaterOrEqualThanThreshold)
        {
            lock (sumLock)
            {
                sum += number;
            }
        }   
    });
    return sum;
}

This code does not compile. The compiler error message is:

Cannot use ref or out parameter 'threshold' inside an anonymous method, lambda expression, or query expression

The goal of this parallel ComputeSum method is to parallely compute the sum of some numbers of the 'numbers' parameter list. This sum will include all the numbers that are greater or equal to the referenced threshold ref parameter.

This threshold parameter is passed as a ref because it can be modified by some other tasks during the ComputeSum method execution, and I need each number comparaison to be made with the current threshold value at the time at which the comparaison with the threshold is made. (I know, in this simplified example it may appear silly to do this but the actual code is more complex and makes sense).

My question is: What workaround can I use to access the threshold by ref inside the Parallel.ForEach lambda-expression statement ?

Note: I read the "said duplicate" question Cannot use ref or out parameter in lambda expressions but I'm not asking why this ref parameter access is refused by the compiler but I'm asking for a workaround to do what I intend to do.

Community
  • 1
  • 1
Bug Raptor
  • 261
  • 6
  • 15
  • 1
    This is not good way to make sum in Parallel loop. Better do it synchronously this locks destroy every parallel operation. I can show you some code but your question is closed. Check Interlocked class – mybirthname Nov 11 '16 at 07:14
  • My actual code does not compute a sum, (it's a more complex recusrsive parallel code evaluating a node of a two players game tree). I exposed this simplified code to explain my ref parameter accessing issue. – Bug Raptor Nov 11 '16 at 07:40
  • @Christos: Note: I read the "said duplicate" question [Cannot use ref or out parameter in lambda expressions][1] but I'm not asking why this ref parameter access is refused by the compiler but I'm asking for a workaround to do what I intend to do. – Bug Raptor Nov 11 '16 at 07:46
  • @BugRaptor I just reopened it. – Christos Nov 11 '16 at 08:05
  • Locking inside a Parallel loop converts it to a very slow sequential process and is pointless. You *shouldn't* need to use locking or out parameters - are you really going to write to the same parameter 10 times but keep only one of the values at random? That's what happens when you write on a scalar value from inside any loop – Panagiotis Kanavos Nov 11 '16 at 08:09
  • Post code that actually demonstrates your problem. The problem isn't that you want to use a `ref`, it's the ref in the first place, and the fact that some random task modifies it instead of returning a value. At the very least it appears that you have a method that tries to do too many things at once – Panagiotis Kanavos Nov 11 '16 at 08:11
  • On the other hand, a ref or a field are still external state. Why not extract your computation to a separate class and use a *field* to hold the current value? Or use a wrapper class whose `Current` property returns the current threshold instead of a ref double? – Panagiotis Kanavos Nov 11 '16 at 08:14

1 Answers1

1

Here is the solution I found:

The point was to wrap the shared double value (threshold) into a class (that can also implement mutual exclusion) and pass this object as parameter to the parallel computing method including the Parallel.ForEach statement.

The code is now more clear and works like I intended. (Each access to the threshold value refers the last updated value.)

The generic SharedVariable<T> class protects a value of any type against concurrent reading/writing threads.

Note the use of a ReaderWriterLockSlim lock to prevent the readers to lock themselves when concurrently reading the variable's value.

(Only Writer threads require exclusive access to the variable's value).

public class SharedVariable<T>
{
    // The shared value:
    private T value;
    
    // The ReaderWriterLockSlim instance protecting concurrent access to the shared variable's value:
    private ReaderWriterLockSlim readerWriterLock = new ReaderWriterLockSlim();
    
    // Constructor
    public SharedVariable(T val)
    {
        this.value = val;
    }

    // Gets or sets the value with thread-safe locking and notifying value changes 
    public T Value 
    {
        get
        {
            readerWriterLock.EnterReadLock();
            try
            {
                return value;
            }
            finally
            {
                readerWriterLock.ExitReadLock();
            }
        }
    
        set
        {
            readerWriterLock.EnterWriteLock();
            try
            {
                if (!this.value.Equals(value))
                {
                    this.value = value;
                }
            }
            finally
            {
                readerWriterLock.ExitWriteLock();
            }
        }
    }
    
    // GetAndSet allows to thread-safely read and change the shared variable's value as an atomic operation. 
    // The update parameter is a lamda expression computing the new value from the old one. 
    // Example: 
    // SharedVariable<int> sharedVariable = new SharedVariable<int>(0);
    // sharedVariable.GetAndSet((v) => v + 1);  // Increments the sharedVariable's Value.
    public void GetAndSet(Func<T,T> update)
    {
        readerWriterLock.EnterWriteLock();
        try
        {
            T newValue = update(this.value);
            if (!this.value.Equals(newValue))
            {
                this.value = newValue;
            }
        }
        finally
        {
            readerWriterLock.ExitWriteLock();
        }
    }
}

public double ComputeSum(List<double> numbers, SharedVariable<double> thresholdValue)
{
    SharedVariable<double> sum = new SharedVariable<double>(0);

    Parallel.ForEach (numbers, (number) => 
    {
        if (number >= thresholdValue.Value)
        {
            sum.GetAndSet((v) => v + number);
        }   
    });
    return sum.Value;
}
CarenRose
  • 1,266
  • 1
  • 12
  • 24
Bug Raptor
  • 261
  • 6
  • 15
  • It's very bad approach. See [Parallel Aggregation](https://msdn.microsoft.com/en-us/library/ff963547.aspx). – Alexander Petrov Nov 11 '16 at 10:57
  • 2
    If you mean that parallely computing a sum of numbers this way is a bad approach, I totally agree ! Again this was only a (badly choosen) example of parallel processing on the list elements. Actually my list is not a list of numbers and I'm not parallely computing a sum (or any other kind of agregation) on these list elements. I'am actually performing the same computing process on each list element. I choosed this bad example only to make the sample code appear simpler. I only wanted to ask how each ParallelFor loop iteration could access a shared non fixed value. – Bug Raptor Nov 11 '16 at 14:27