如何从 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;
}
考虑我的以下(简化)代码:
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;
}