您有几个选择。最简单的方法是创建单个State 对象,并使用lock 同步对其的访问:
var state = new State();
Parallel.ForEach(folders, config, source =>
{
var content = File.ReadAllText(source);
string result;
lock (state) { result = state.DoSomething(content); }
File.WriteAllText(outputFile, result);
});
我认为这是不可行的,因为 DoSomething 方法很耗时,并且同步它会破坏并行性。
另一种选择是使用ThreadLocal<State>。此类提供数据的线程本地存储,因此创建的State 对象的数量将等于Parallel.ForEach 使用的线程数量。
var threadLocalState = new ThreadLocal<State>(() => new State());
Parallel.ForEach(folders, config, source =>
{
var content = File.ReadAllText(source);
var result = threadLocalState.Value.DoSomething(content);
File.WriteAllText(outputFile, result);
});
这可能会比Parallel.ForEach<TSource, TLocal> 重载创建更少的State 对象,但仍不等于配置的MaxDegreeOfParallelism。 Parallel.ForEach 使用来自ThreadPool 的线程,如果folders 的列表足够长,它很有可能在计算过程中使用所有线程。而且您几乎无法控制ThreadPool 的大小。所以这也不是一个特别诱人的解决方案。
我能想到的第三个也是最后一个选项是创建一个由State 对象组成的池,并在每个循环中创建一个Rent/Return:
var statePool = new ObjectPool<State>(() => new State());
Parallel.ForEach(folders, config, source =>
{
var state = statePool.Rent();
var content = File.ReadAllText(source);
var result = state.DoSomething(content);
File.WriteAllText(outputFile, result);
statePool.Return(state);
});
这样实例化的State对象的数量将等于最大并行度。
唯一的问题是.NET 平台中没有ObjectPool 类(只有一个ArrayPool 类),所以您必须找到一个。这是一个基于ConcurrentBag的简单实现:
public class ObjectPool<T> : IEnumerable<T> where T : new()
{
private readonly ConcurrentBag<T> _bag = new ConcurrentBag<T>();
private readonly Func<T> _factory;
public ObjectPool(Func<T> factory = null) => _factory = factory;
public T Rent()
{
if (_bag.TryTake(out var obj)) return obj;
return _factory != null ? _factory() : new T();
}
public void Return(T obj) => _bag.Add(obj);
public IEnumerator<T> GetEnumerator() => _bag.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}