如何从Parallel.For或Parallel.ForEach lambda表达式

时间:2016-11-11 07:01:29

标签: c# ref out parallel.foreach parallel.for

考虑我的以下(简化)代码:

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;
}

此代码无法编译。 编译器错误消息是:

无法在匿名方法,lambda表达式或查询表达式中使用ref或out参数'threshold'

这种并行ComputeSum方法的目标是平行计算一些“数字”参数列表的总和。此总和将包括大于或等于参考阈值ref参数的所有数字。

此阈值参数作为ref传递,因为它可以在ComputeSum方法执行期间通过其他任务进行修改,并且我需要使用当前阈值进行每个数字比较在与阈值进行比较的时候。 (我知道,在这个简化的例子中,这样做可能看起来很愚蠢,但实际的代码更复杂,更有意义。)

我的问题是:在Parallel.ForEach lambda-expression语句中,我可以通过ref使用什么解决方法来访问阈值?

注意:我读了“复制”问题Cannot use ref or out parameter in lambda expressions,但我不是在问为什么编译器会拒绝这个ref参数访问,但是我要求一个解决方法来做我打算做的事情。

1 个答案:

答案 0 :(得分:0)

以下是我找到的解决方案:

关键是将共享double值(threshold)包装到一个类中(也可以实现互斥),并将此对象作为参数传递给并行计算方法,包括Parallel.ForEach语句。

代码现在更加清晰,并且像我预期的那样工作。 (每次访问阈值都是指最后更新的值。)

通用SharedVariable&lt; T> class保护任何类型的值不受并发读/写线程的影响。

注意使用ReaderWriterLockSlim锁来防止读者在同时读取变量的值时锁定自己。

(只有Writer线程需要独占访问变量的值)。

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;
}