让我们说你有一个在后台线程上运行的简单操作.您希望提供一种取消此操作的方法,以便创建一个布尔标志,您可以从取消按钮的单击事件处理程序设置为true.
private bool _cancelled;
private void CancelButton_Click(Object sender ClickEventArgs e)
{
_cancelled = true;
}
Run Code Online (Sandbox Code Playgroud)
现在您正在从GUI线程设置取消标志,但是您正在从后台线程中读取它.在访问bool之前你需要锁定吗?
你需要这样做(显然也锁定了按钮点击事件处理程序):
while(operationNotComplete)
{
// Do complex operation
lock(_lockObject)
{
if(_cancelled)
{
break;
}
}
}
Run Code Online (Sandbox Code Playgroud)
或者这样做是否可以接受(没有锁定):
while(!_cancelled & operationNotComplete)
{
// Do complex operation
}
Run Code Online (Sandbox Code Playgroud)
或者将_cancelled变量标记为volatile.这有必要吗?
[我知道BackgroundWorker类有内置的CancelAsync()方法,但是我对这里的语义和锁定和线程变量访问的使用感兴趣,而不是具体的实现,代码只是一个例子.
似乎有两种理论.
1)因为它是一个简单的内置类型(并且对内置类型的访问在.net中是原子的)并且因为我们只在一个地方写入它并且只在后台线程上读取而不需要锁定或标记为volatile.
2)你应该将它标记为volatile,因为如果你不这样做,编译器可能会优化while循环中的读取,因为它认为它无法修改该值.
哪种方法正确?(为什么?)
[编辑:在这方面似乎有两个明确定义和对立的思想流派.我正在寻找一个明确的答案,所以请尽可能发布您的理由并引用您的消息来源和您的答案.]
我很抱歉有一个多余的问题.但是,我找到了很多解决我问题的方法,但没有一个解释得很清楚.我希望在这里说清楚.
我的C#应用程序的主线程使用ThreadPool生成1..n后台工作者.我希望原始线程锁定,直到所有工人都完成.我特别研究了ManualResetEvent,但我不清楚它的用途.
在伪:
foreach( var o in collection )
{
queue new worker(o);
}
while( workers not completed ) { continue; }
Run Code Online (Sandbox Code Playgroud)
如果有必要,我会知道即将排队的工人数量.
C#中的字符串是不可变的和线程安全的.但是当你有一个公共吸气剂属性时呢?像这样:
public String SampleProperty{
get;
private set;
}
Run Code Online (Sandbox Code Playgroud)
如果我们有两个线程,第一个是调用'get'而第二个是在"相同"时调用'set',会发生什么?
恕我直言,该集必须锁定为线程安全,如下所示:
private string sampleField;
private object threadSafer = new object();
public String SampleProperty{
get{ return this.sampleField; }
private set{
lock(threadSafer){
sampleField = value;
}
}
}
Run Code Online (Sandbox Code Playgroud) 人们普遍接受(我相信!)a lock会强制重新加载字段中的任何值(基本上充当内存屏障或栅栏 - 我在这方面的术语有点松散,我害怕),结果那些只能在一个内部访问过的字段lock本身并不需要volatile.
(如果我错了,就说!)
这里提出了一个很好的评论,质疑如果代码执行是否也是如此Wait()- 即一旦它已经Pulse()d,它将从内存重新加载字段,还是可以在寄存器(等)中.
或者更简单:该字段是否需要volatile确保在a后恢复时获得当前值Wait()?
看着反射器,Wait召唤进入ObjWait,这是managed internalcall(相同 Enter).
有问题的情景是:
bool closing;
public bool TryDequeue(out T value) {
lock (queue) { // arbitrary lock-object (a private readonly ref-type)
while (queue.Count == 0) {
if (closing) { // <==== (2) access field here
value = default(T);
return false;
}
Monitor.Wait(queue); // <==== (1) waits here
} …Run Code Online (Sandbox Code Playgroud) 我对这个主题非常困惑 - 读取/切换bool值是否是线程安全的.
// case one, nothing
private bool v1;
public bool V1 { get { return v1; } set { v1 = value; } }
// case two, with Interlocked on set
private int v2;
public int V2 { get { return v2; } set { Interlocked.Exchange(ref v2, value); } }
// case three, with lock on set
private object fieldLock = new object();
private bool v3;
public bool V3 { get { return v3; } set { lock …Run Code Online (Sandbox Code Playgroud) 我担心看似标准的前C#6模式的正确性可以解雇事件:
EventHandler localCopy = SomeEvent;
if (localCopy != null)
localCopy(this, args);
Run Code Online (Sandbox Code Playgroud)
我已经阅读了Eric Lippert的事件和比赛,并且知道调用过时的事件处理程序还有一个问题,但我担心的是,是否允许编译器/ JITter优化掉本地副本,有效地将代码重写为
if (SomeEvent != null)
SomeEvent(this, args);
Run Code Online (Sandbox Code Playgroud)
有可能NullReferenceException.
根据C#语言规范,§3.10,
必须保留这些副作用的顺序的关键执行点是对volatile字段(第10.5.3节),锁语句(第8.12节)以及线程创建和终止的引用.
- 所以在上述模式中没有关键执行点,优化器也不受此限制.
由于条件的原因,JIT不允许在第一部分中执行您正在讨论的优化.我知道这是作为一个幽灵提出的,但它无效.(我刚才和Joe Duffy或Vance Morrison一起检查过;我不记得是哪一个.)
- 但是评论引用了这篇博客文章(2008年):事件和线程(第4部分),它基本上说CLR 2.0的JITter(可能是后续版本?)不能引入读取或写入,所以一定没有问题在Microsoft .NET下.但这似乎与其他.NET实现没有任何关系.
[旁注:我没有看到不引入读取证明了所述模式的正确性.难道JITter只是看到一些SomeEvent其他局部变量的陈旧值并优化其中一个读取,而不是另一个?完全合法,对吗?]
此外,这篇MSDN文章(2012年):Igor Ostrovsky的理论与实践中的C#记忆模型陈述如下:
非重新排序优化某些编译器优化可能会引入或消除某些内存操作.例如,编译器可能用一次读取替换字段的重复读取.类似地,如果代码读取字段并将值存储在局部变量中然后重复读取变量,则编译器可以选择重复读取该字段.
因为ECMA C#规范不排除非重新排序的优化,所以它们可能是允许的.实际上,正如我将在第2部分中讨论的那样,JIT编译器确实执行了这些类型的优化.
这似乎与Jon Skeet的答案相矛盾.
由于现在C#不再是Windows语言,因此问题在于,模式的有效性是否是当前CLR实现中有限的JITter优化的结果,或者是语言的预期属性.
所以,问题是:从C#-the-language的角度来看,正在讨论的模式是否有效?(这意味着是否需要语言编译器/运行时来禁止某种优化.)
当然,欢迎就该主题提出规范性参考.
我正在寻找一个可重现的示例,可以演示volatile关键字的工作原理.我正在寻找一些工作"错误"的东西,而没有标记为易变的变量,并且"正确"地使用它.
我的意思是一些示例,它将证明执行期间的写/读操作顺序与变量未标记为volatile时的预期顺序不同,并且当变量未标记为volatile时不同.
我认为我得到了一个例子但是在其他人的帮助下我意识到它只是一段错误的多线程代码.为什么volatile和MemoryBarrier不会阻止操作重新排序?
我还发现了一个链接,它演示了volatile对优化器的影响,但它与我正在寻找的不同.它表明对标记为volatile的变量的请求不会被优化.如何在C#中说明volatile关键字的用法
这是我到目前为止的地方.此代码未显示任何读/写操作重新排序的迹象.我正在寻找一个会展示的.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
namespace FlipFlop
{
class Program
{
//Declaring these variables
static byte a;
static byte b;
//Track a number of iteration that it took to detect operation reordering.
static long iterations = 0;
static object locker = new object();
//Indicates that operation reordering is not found yet.
static volatile bool continueTrying = true;
//Indicates that Check method should continue.
static volatile bool continueChecking = true;
static …Run Code Online (Sandbox Code Playgroud) private InstrumentInfo[] instrumentInfos = new InstrumentInfo[Constants.MAX_INSTRUMENTS_NUMBER_IN_SYSTEM];
public void SetInstrumentInfo(Instrument instrument, InstrumentInfo info)
{
if (instrument == null || info == null)
{
return;
}
instrumentInfos[instrument.Id] = info; // need to make it visible to other threads!
}
public InstrumentInfo GetInstrumentInfo(Instrument instrument)
{
return instrumentInfos[instrument.Id]; // need to obtain fresh value!
}
Run Code Online (Sandbox Code Playgroud)
SetInstrumentInfo并GetInstrumentInfo从不同的线程调用.
InstrumentInfo是不可变的类.我保证在打电话时有最新的副本GetInstrumentInfo吗?我担心我可以收到"缓存"副本.我应该添加同步吗?
声明instrumentInfos是volatile不会帮助,因为我需要声明数组项为volatile,不是数组本身.
我的代码有问题,如果有问题如何解决?
UPD1:
我需要我的代码在现实生活中工作,不符合所有规范!因此,如果我的代码在现实生活中起作用,但在某些环境下的某些计算机上"理论上"不起作用 - 那没关系!
Thread.MemoryBarrier,除了增加延迟之外什么都不做.我认为我们可以相信微软将在未来版本中继续使用"强大的内存模型".至少微软不太可能改变内存模型.所以我们假设它不会.UPD2: …
这个问题与这个问题有关(使用C#5异步来等待在多个游戏帧上执行的事情).
当Miguel de Icaza在Alt-Dev-Conf 2012上首次为游戏提供C#5异步框架时,我真的很喜欢使用async和await处理"脚本" 的想法(可以这么说,因为它们在c#中,在我的情况下,编译---在时间上,但无论如何编译)在游戏中.
即将推出的Paradox3D游戏引擎似乎也依赖于异步框架来处理脚本,但从我的角度来看,这个想法和实现之间存在着真正的差距.
在链接的相关问题中,有人使用awaitNPC执行一系列指令,而游戏的其余部分仍在运行.
我想更进一步,允许NPC同时执行多个操作,同时以顺序方式表达这些操作.有点像:
class NonPlayableCharacter
{
void Perform()
{
Task walking = Walk(destination); // Start walking
Task sleeping = FallAsleep(1000); // Start sleeping but still walks
Task speaking = Speak("I'm a sleepwalker"); // Start speaking
await walking; // Wait till we stop moving.
await sleeping; // Wait till we wake up.
await speaking; // Wait till …Run Code Online (Sandbox Code Playgroud) 根据我的理解,C#中的'volatile'修饰符有两个效果:
在x86/amd64上,(1)无关紧要.这些处理器不需要用于易失性语义的围栏.(虽然ia64不同.)
所以,我们归结为(2).但是,对于我尝试过的例子,volatile对jit-ted程序集没有任何影响.
我的问题是:你能举一个C#代码示例的例子,在字段上添加'volatile'修饰符会导致不同的jit-ted汇编代码吗?
在我的wpf应用程序中,使用单独的线程调用viewmodel中的耗时操作.但是,此函数访问视图模型中绑定到视图中对象的多个属性.我尝试直接访问它们,我看不到有关它们归UI线程所有的投诉.我很想知道在线程之间直接使用它们的后果.
在这篇文章中:
http://msdn.microsoft.com/en-us/magazine/jj883956.aspx
作者声明,由于"循环读取提升",以下代码可能会失败:
class Test
{
private bool _flag = true;
public void Run()
{
// Set _flag to false on another thread
new Thread(() => { _flag = false; }).Start();
// Poll the _flag field until it is set to false
while (_flag) ;
// The loop might never terminate!
}
}
Run Code Online (Sandbox Code Playgroud)
在循环读取提升中,由于单线程假设,编译器可能会将上面的while循环更改为以下内容:
if (_flag) { while (true); }
Run Code Online (Sandbox Code Playgroud)
我想知道的是:如果编译器没有执行该优化,由于一个处理器更新寄存器或缓存中的_flag并且从不将该缓存刷新回到多处理器机器上,因此循环仍有可能永远在多处理器机器上运行内存可以被其他线程读取?我已经读到"C#写入是易失性的",但是我链接的文章说ECMA规范实际上并没有保证这一点,并且事情没有在ARM上实现.我试图弄清楚我必须要编写一些可以在所有平台上运行的线程代码.
这是一个相关的问题:
但我认为接受的答案中的代码可能会通过循环读取提升进行优化,因此它无法证明内存可见性......
我有一个简单的C#foreach循环,当按下按钮时如何突破循环?它不在backgroundWorker线程中,因此我无法使用backgroundWorker.CancellationPending.
c# ×12
.net ×7
volatile ×3
asynchronous ×1
c#-5.0 ×1
caching ×1
concurrency ×1
foreach ×1
locking ×1
memory-model ×1
monitor ×1
mono ×1
mvvm ×1
wpf ×1