假设我们有POD类型:
private class Messages {
public byte[] last;
public byte[] next;
}
及其实例messages
。
当用户(调用者)请求实例时,我们希望给他一个消息对象的深层副本(可能不是最新的)。当用户设置自己的版本时,我们希望尽可能将其提供给其他人,但不要中断读取请求(应删除旧版本,而不是尽快中断读取)。
如何使用System.Collections.Concurrent进行此类对象版本控制?
我尝试了什么:
internal class CuncurrentMessagesHelper {
private readonly ConcurrentStack<Messages> _stack = new ConcurrentStack<Messages>();
public CuncurrentMessagesHelper() {
}
public void SetLatest(Messages m) {
var length = _stack.Count;
_stack.Push(m);
var range = new Messages[length];
_stack.TryPopRange(range, 0, length);
}
public bool ReadLatest(out Messages result) {
return _stack.TryPeek(out result);
}
}
然而,这种帮助方法似乎是丑陋的黑客。
答案 0 :(得分:2)
这不是POD。这是一个POCO。我建议你阅读.NET的值类型和引用类型之间的区别,因为在编写安全的并发代码时它们的语义是至关重要的。
由于C#引用保证是原子的,因此解决方案很简单(并且不需要任何特殊的并发容器)。
假设您的Messages
对象在传入后是不可变的:
internal class ConcurrentMessagesHelper {
private volatile Messages _current;
public void SetLatest(Messages m) {
_current = m;
}
public Messages ReadLatest() {
return _current;
}
}
请注意,它是对此处(原子地)复制的对象的引用,而不是对象的byte[]
字段。 volatile
是必需的,因为引用是由多个线程访问的(它确保了正确的行为,特别是关于内存排序和限制JIT只能执行线程安全的优化)。
如果传递给Messages
的{{1}}对象在最新状态下可以更改,那么您所要做的就是先复制一份。 SetLatest
成为:
SetLatest
如果允许读者更改返回的public void SetLatest(Messages m) {
_current = DeepClone(m);
}
对象,则必须先复制它,然后再让它们使用。 Messages
成为:
ReadLatest
请注意,如果public Messages ReadLatest() {
return DeepClone(_current);
}
byte[]
字段中包含的值在每个邮件的生命周期内都是不可变的,那么您只需要一个浅表副本,而不是深层副本。
通过将界面包装在一个简单的属性中,您可以使界面更加出色:
Messages
如果实际确实有POD类型(例如internal class ConcurrentMessagesHelper {
private volatile Messages _current;
public Messages Current {
get { return DeepClone(_current); }
set { _current = DeepClone(value); }
}
private static Messages DeepClone(Messages m)
{
if (m == null)
return null;
return new Messages {
last = m.last == null ? null : (byte[])m.last.Clone(),
next = m.next == null ? null : (byte[])m.next.Clone()
};
}
}
),那么我建议最简单的解决方案是将它包装在一个类中,这样你就可以有一个原子引用它的副本,这将允许您使用上面的解决方案。想到struct Messages
。
这种情况下的代码变得更加简单,因为不需要显式复制:
StrongBox<T>
如果private struct Messages {
public byte[] last;
public byte[] next;
}
internal class ConcurrentMessagesHelper {
private volatile StrongBox<Messages> _current;
public Messages Current {
get { return _current.Value; }
set { _current = new StrongBox<Messages>(value); }
}
}
中的字节数组在对象的生命周期内发生变化,那么我们仍然需要进行深度克隆:
Messages