如何从 Parallel.For 或 Parallel.For 每个 lambda 表达式访问 ref 或 out 参数

how to access a ref or out parameter from Parallel.For or Parallel.ForEach lambda expression

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

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 方法的目标是并行计算 'numbers' 参数列表中某些数字的总和。此总和将包括所有大于或等于引用阈值 ref 参数的数字。

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

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

注意:我阅读了 "said duplicate" 问题 Cannot use ref or out parameter in lambda expressions,但我不是在问为什么编译器拒绝此 ref 参数访问,而是在寻求一种解决方法来实现我的意图要做。

这是我找到的解决方案:

重点是将共享的double值(阈值)包装成一个class(也可以实现互斥)并将这个对象作为参数传递给并行计算方法,包括Parallel.ForEach声明。

代码现在更加清晰并且可以按我的预期工作。 (每次访问阈值都是指最后一次更新的值。)

泛型 SharedVariable<T> class 保护任何类型的值免受并发 reading/writing 线程的影响。

注意使用 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;
}