Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/263.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 以线程安全的方式更改和读取ConcurrentDictionary中对象的属性_C#_Multithreading_Concurrency - Fatal编程技术网

C# 以线程安全的方式更改和读取ConcurrentDictionary中对象的属性

C# 以线程安全的方式更改和读取ConcurrentDictionary中对象的属性,c#,multithreading,concurrency,C#,Multithreading,Concurrency,我使用ConcurrentDictionary在WebAPI应用程序中收集内存中的数据。我使用api方法在ConcurrentDictionary中添加和更新对象。还有一个后台线程根据对象属性分析和清理这个字典。现在我考虑两种方法: 1.在AddOrUpdate方法中的updateValueFactory中使用字典项锁定,但问题是如何正确读取属性,以确保我拥有它的最新版本,并且我没有在非稳定状态下读取属性 public class ThreadsafeService2 { private

我使用ConcurrentDictionary在WebAPI应用程序中收集内存中的数据。我使用api方法在ConcurrentDictionary中添加和更新对象。还有一个后台线程根据对象属性分析和清理这个字典。现在我考虑两种方法: 1.在AddOrUpdate方法中的updateValueFactory中使用字典项锁定,但问题是如何正确读取属性,以确保我拥有它的最新版本,并且我没有在非稳定状态下读取属性

public class ThreadsafeService2
{
    private readonly ConcurrentDictionary<string, ThreadSafeItem2> _storage = 
        new ConcurrentDictionary<string, ThreadSafeItem2>();

    public void AddOrUpdate(string name)
    {
        var newVal = new ThreadSafeItem2();
        _storage.AddOrUpdate(name, newVal, (key, oldVal) =>
        {
            //use lock
            lock (oldVal)
            {
                oldVal.Increment();
            }
            return oldVal;
        });
    }

    public void Analyze()
    {
        foreach (var key in _storage.Keys)
        {
            if (_storage.TryGetValue(key, out var item))
            {
                //how to read it properly?
                long ticks = item.ModifiedTicks;
            }
        }
    }
}
public class ThreadSafeItem2
{
    private long _modifiedTicks;
    private int _counter;
    public void Increment()
    {
        //no interlocked here
        _modifiedTicks = DateTime.Now.Ticks;
        _counter++;
    }

    //now interlocked here
    public long ModifiedTicks => _modifiedTicks;
    public int Counter => _counter;
}

这里的最佳实践是什么?

我无法评论您具体在做什么,但联锁和并发字典比您自己做的锁更好


不过,我会质疑这种做法。您的数据足够重要,但不重要到可以保存它吗?根据应用程序的使用情况,这种方法会在一定程度上降低应用程序的速度。同样,由于不知道您正在做什么,您可以将每个添加都抛出到MSMQ中,然后按照某个计划运行一个外部exe来处理这些项目。该网站只需启动并忘记,没有线程要求。

因此,您的两个实现都存在重大问题。第一个解决方案在递增时锁定,但在读取时不锁定,这意味着访问数据的其他位置可以读取无效状态

一个非技术性的问题,但仍然是一个主要的问题,就是您已经将类命名为ThreadSaveItem,但实际上它并不是设计为可以从多个线程安全访问的。在这个实现中,调用者的责任是确保不从多个线程访问该项。如果我看到一个名为ThreadSafeItem的类,我将假设从多个线程访问它是安全的,并且只要我执行的每个操作都是逻辑原子的,我就不需要同步对它的访问

您的联锁解决方案是有问题的,因为您必须修改要修改的字段,这些字段在概念上是绑定在一起的,但您没有将它们的更改同步在一起,这意味着有人可以观察到对其中一个字段的修改,而不是对另一个字段的修改,这是该代码的问题

其次,在这两种解决方案中使用AddOrUpdate并不合适。方法调用的全部要点是添加一个项或用另一个项替换它,而不是改变提供的项,这就是它接受返回值的原因;你应该生产一种新产品。如果您想使用获取可变项并对其进行变异的方法,那么可以调用GetOrAdd来获取现有项或创建新项,然后使用返回值以线程安全的方式对其进行变异

通过简单地使ThreadSafeItem不可变,整个解决方案从根本上得到了简化。它允许您在ConcurrentDictionary上使用AddOrUpdate进行更新,这意味着需要进行的唯一同步是更新ConcurrentDictionary的值,并且它已经处理了自身状态的同步,在访问ThreadSafeItem时根本不需要进行同步,因为对数据的所有访问本质上是线程安全的,因为它是不可变的。这意味着您实际上根本不需要编写任何同步代码,这正是您希望尽可能争取的

最后,我们有实际的代码:

public class ThreadsafeService3
{
    private readonly ConcurrentDictionary<string, ThreadSafeItem3> _storage =
        new ConcurrentDictionary<string, ThreadSafeItem3>();

    public void AddOrUpdate(string name)
    {
        _storage.AddOrUpdate(name, _ => new ThreadSafeItem3(), (_, oldValue) => oldValue.Increment());
    }

    public void Analyze()
    {
        foreach (var pair in _storage)
        {
            long ticks = pair.Value.ModifiedTicks;
            //Note, the value may have been updated since we checked; 
            //you've said you don't care and it's okay for a newer item to be removed here if it loses the race.
            if (isTooOld(ticks))
                _storage.TryRemove(pair.Key, out _);  
        }
    }
}

public class ThreadSafeItem3
{
    public ThreadSafeItem3()
    {
        Counter = 0;
    }
    private ThreadSafeItem3(int counter)
    {
        Counter = counter;
    }
    public ThreadSafeItem3 Increment()
    {
        return new ThreadSafeItem3(Counter + 1);
    }

    public long ModifiedTicks { get; } = DateTime.Now.Ticks;
    public int Counter { get; }
}

我认为这取决于Analyze如何处理这些值。在阅读long ticks=item.ModifiedTicks之后使用第二种方法,此值可能已经过时,另一个线程可能已经在阅读后更新了它。取决于您如何使用这些值,这可能是一个问题,也可能不是。@Evk我将它与某个阈值进行比较,然后决定:是否从字典中删除它。此外,在工作项目中,我在ThreadSafeItem中还有一个方法,它根据计数器和modifiedticks值返回bool值。所以,如果您只增加修改的刻度,并根据modifiedticks>threshold删除项目,那么您就不关心过时的刻度值。另一方面,如果您基于ModifiedTicks几分钟后,我就可以阅读它并做出决定——我是否应该删除它。如果某个线程稍后会更新它,那就太晚了。我只想确保以一致且线程安全的方式读取和写入项中的属性。客户端经常调用此api,因此我在将其放入数据库之前在内存中进行了一些预聚合。基于此,如果使用队列对其进行聚合,然后将其放入数据库,则不会遇到任何缩放或性能问题。它还可以与数据库进行事务处理。此服务具有可扩展的客户端库。使用任何线程锁都不会使任何应用程序具有可扩展性。您告诉所有线程停止并等待操作完成。使用Interlock和Concurrent Dictionary比手动操作做得更好。@acivic2nv线程仅在出现争用时停止并等待。在绝大多数情况下,锁不是高度上下文扩展的,因此对性能几乎没有影响。此外,ConcurrentDictionary也使用锁,它只是尽可能不频繁地使用它们。同样,Interlocated在高争用情况下也有自己的开销。当然,在你找到有效的解决方案之前,所有这些都是无关紧要的。OP的两种解决方案实际上并不安全。唯一正确的解决方案是锁定这两个操作的全部。由于内存分配非常密集,GC是否可能出现任何问题?@mtkachenko请随意分析代码,并查看使用此解决方案是否会导致性能问题。一旦它被设置为不可变的,该对象将值得考虑成为值类型;如果您的探查器得出了明显的证据,表明额外的分配导致了足够多的额外GC压力成为问题,我高度怀疑这种情况是否会发生,但这并非不可能,那么您可以考虑将ThreadSafeItem设置为值类型;这当然是一个整体的工作。我不会搞砸它,除非有一个合理的问题。
public class ThreadsafeService3
{
    private readonly ConcurrentDictionary<string, ThreadSafeItem3> _storage =
        new ConcurrentDictionary<string, ThreadSafeItem3>();

    public void AddOrUpdate(string name)
    {
        _storage.AddOrUpdate(name, _ => new ThreadSafeItem3(), (_, oldValue) => oldValue.Increment());
    }

    public void Analyze()
    {
        foreach (var pair in _storage)
        {
            long ticks = pair.Value.ModifiedTicks;
            //Note, the value may have been updated since we checked; 
            //you've said you don't care and it's okay for a newer item to be removed here if it loses the race.
            if (isTooOld(ticks))
                _storage.TryRemove(pair.Key, out _);  
        }
    }
}

public class ThreadSafeItem3
{
    public ThreadSafeItem3()
    {
        Counter = 0;
    }
    private ThreadSafeItem3(int counter)
    {
        Counter = counter;
    }
    public ThreadSafeItem3 Increment()
    {
        return new ThreadSafeItem3(Counter + 1);
    }

    public long ModifiedTicks { get; } = DateTime.Now.Ticks;
    public int Counter { get; }
}