1

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

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 参数“阈值”

此并行 ComputeSum 方法的目标是并行计算“数字”参数列表中某些数字的总和。此总和将包括所有大于或等于引用的阈值 ref 参数的数字。

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

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

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

4

1 回答 1

0

这是我找到的解决方案:

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

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

通用 SharedVariable< T > 类保护任何类型的值免受并发读/写线程的影响。

请注意使用 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;
}
于 2016-11-11T09:43:28.253 回答