Foreach循环和带锁定的变量

时间:2011-07-19 00:54:13

标签: c# task-parallel-library

基本上我想做的就是这个伪代码

List<DatabaseRecord> records;

List<ChangedItem> changedItems;

Parallel.ForEach<DatabaseRecord>(records, (item, loopState) =>
{
    if (item.HasChanged)
    {
        lock (changedItems)
        {
            changedItems.Add(new ChangedItem(item));
        }
    }
});

但我担心的是锁定changedItems。虽然它有效,但我听说它必须一遍又一遍地序列化锁定的对象。有没有更好的方法呢?

4 个答案:

答案 0 :(得分:9)

为什么不使用PLinq呢?无需锁定:

changedItems = records.AsParallel()
                      .Where(x => x.HasChanged)
                      .Select(x => new ChangedItem(x))
                      .ToList();

由于您要投射到ChangedItem的新列表并且没有任何副作用,这将是我认为的方式。

答案 1 :(得分:1)

我认为锁定列表不会导致列表序列化/反序列化,因为锁定发生在所有对象上可用的私有字段上(syncBlockIndex)。但是,锁定的recommended way是创建一个专门用于锁定的私有字段:

object _lock = new object();

这是因为您可以控制锁定的内容。如果您通过属性发布对列表的访问权限,那么控件之外的代码可能会锁定该对象,从而引入死锁情况。

关于PLinq,我认为决定使用它取决于你的主机和负载是什么样的。例如,在ASP.NET中,PLINQ is more processor hungry可以更快地完成它,但代价是不再处理其他Web请求。无可否认,语法更加清晰。

答案 2 :(得分:1)

您似乎在单线程中使用此代码? 如果它是单线程,则不需要锁定。 删除行“lock(changedItems)”时它是否正常工作 @BrokenGlass发布的代码更清晰易懂。

答案 3 :(得分:1)

您可以为changedItems使用ConcurrentCollection吗?像ConcurrentQueue这样的东西?然后你根本不需要锁定。

<强>更新

关于ConcurrentQueue,Enqueue不会阻止线程保持操作线程安全。它使用SpinWait ...

保持用户模式
public void Enqueue(T item)
{
    SpinWait wait = new SpinWait();
    while (!this.m_tail.TryAppend(item, ref this.m_tail))
    {
        wait.SpinOnce();
    }
}