是无法解决的”;旗帜;C#/.NET中的线程安全?
(注意:我已经问过这个问题,但是答案是针对Java的,所以我对C#和.NET framework问了同样的问题。这不是重复的。) 我已经使用这个模式有一段时间了,但我最近才开始认为这样做可能不好。基本上,我使用这种模式的一些变体:是无法解决的”;旗帜;C#/.NET中的线程安全?,c#,.net,asynchronous,thread-safety,C#,.net,Asynchronous,Thread Safety,(注意:我已经问过这个问题,但是答案是针对Java的,所以我对C#和.NET framework问了同样的问题。这不是重复的。) 我已经使用这个模式有一段时间了,但我最近才开始认为这样做可能不好。基本上,我使用这种模式的一些变体: public class SampleAsync { public SampleAsync() { } private bool completed; public void Start() { var worker
public class SampleAsync
{
public SampleAsync() { }
private bool completed;
public void Start()
{
var worker = new BackgroundWorker();
worker.DoWork += (sender, e) => {
//... do something on a different thread
completed = true;
};
worker.RunWorkerAsync();
}
public void Update()
{
if (!completed) return;
//... do something else
}
}
*用户负责确保只调用一次Start
<无论何时何地都会调用代码>更新
我一直认为这在C#/the.NET framework中是线程安全的,因为即使没有严格同步,我也只将
completed
设置为true。一旦观察到它是true
,它将不会重置为false
。它在构造函数中被初始化为false,根据定义这是线程安全的(除非你在其中做了一些愚蠢的事情)。那么,以这种方式使用不可设置的标志是线程安全的吗?(如果是,它是否提供了任何性能优势?)
谢谢您的代码是线程安全的,因为
bool
是一种原子类型
MSDN:
以下数据类型的读写是原子的:bool、char、,
byte、sbyte、short、ushort、uint、int、float和引用类型。在里面
添加、读取和写入中具有基础类型的枚举类型
前面的列表也是原子的。其他类型的读写,
包括long、ulong、double和decimal,以及用户定义的
类型不能保证是原子的。除了图书馆
为此目的而设计的功能,不保证原子
读修改写,例如在递增或递减的情况下
见:
请用volatile
标记您的字段:
private volatile bool completed;
MSDN:
volatile关键字表示可以在中修改字段
通过诸如操作系统、硬件或
并发执行线程
请参阅:它严重依赖于目标体系结构。英特尔处理器有一个强大的内存模型,所以你倾向于这样的代码。但是抖动可能会把你搞砸。例如,x86 jitter易于将变量存储在cpu寄存器中,特别是当if()语句出现在紧循环中时。而且只有在发布版本中才能做到这一点,这是一场可怕的调试噩梦。声明变量易失性是一个带帮助。x64抖动不需要这样,至少在其当前版本中是这样。但在ARM和安腾等内存较弱的处理器上,创可贴往往无法阻止出血。他们当然不能保证变量的更新状态很快在另一个线程中可见。线程调度程序倾向于刷新cpu缓存。最终
做得不对是没有意义的。使用适当的同步对象,如AutoResteEvent。或者互锁。如果您担心周期,请比较Exchange()。在C#和Java中?否则它是线程安全的吗?如果不使用
volatile
,则“一旦观察到它为真”可能永远不会发生。为什么不使用后台工作人员的IsBusy
属性来确定它是否已完成?我可以想象,他们确保可以从任何线程安全地访问它。@Servy我并不是真的在问BackgroundWorker
——这只是为了演示。关键是completed
在不同的线程中设置,不一定在线程生命周期结束时设置。@overyou00我的观点是这就是为什么存在BackgroundWorker
类的具体原因;帮助管理最常见用例中线程之间的同步。您应该学会利用这一点……但如果您使用[FieldOffset]
或[StructLayout]
显式对齐字段,则所有赌注都将无效。请参阅。这是否保证对该字段的写入会传播到其他线程?我不这么认为。该值可能已注册。对已完成的的写入本身可能是原子的且“安全的”,但如果更新将已完成的的值缓存在寄存器中(或其他具有相同效果的转换),则更新可能永远看不到该值。@usr:你是对的。这就是为什么程序员应该使用volatile
来防止缓存。@virtlink:这不是100%正确的。所有布尔值True
都是相同的:它们都按位等于新的Int32(1)。否则,当使用Object.Equals时,VB.NET库中的True
将不等于C库中的True
。我只是尝试了一个显式布局的结构,它有一个bool和一个int相互重叠。所以OP实际上仍然在修改一位。但是我想你是对的,如果布尔值中的任何位被设置为两次写入操作的一部分,那么Update
方法仍然会收到正确的信号。volatile是否保证直接存储字段并始终从内存重新加载?(这两个属性都是实现此功能所必需的)。我不清楚volatile到底提供了什么保证。@usr:你还需要另一个属性——它不是提前写的,也不是推测性的。+1你的最后一段总结得很好。你不应该问什么东西是线程安全的。使用同步对象并了解。