我理解Thread.Abort()在我读过的关于这个主题的大量文章中是邪恶的,所以我目前正在扯掉我的中止,以便以更清洁的方式取代它; 并且在比较了stackoverflow上的人们的用户策略之后,然后在阅读MSDN上的" 如何:创建和终止线程(C#编程指南) "之后,两者都说明了一种非常相似的方法 - 即使用volatile bool方法检查策略,这很好,但我还有几个问题....
如果你没有一个简单的工作进程只是运行一个运算循环的代码,那么我的优势就在于此.比如说对我来说,我的进程是一个后台文件上传程序进程,我实际上是循环遍历每个文件,所以这是一些东西,并确保我可以while (!_shouldStop)在顶部添加我的每个循环迭代,但我有更多的业务流程它发生在它下一次循环迭代之前发生,我希望这个取消程序是快节奏的; 不要告诉我,我需要在整个工作人员功能中每隔4-5行循环播放这些内容吗?!
我真的希望有一个更好的方法,有人可以告诉我这是否实际上是正确的[并且只有?]做到这一点的方法,或者他们过去用来实现我所追求的目标的策略.
谢谢帮派.
进一步阅读:所有这些SO响应都假设工作线程将循环.这并不适合我.如果它是线性的,但是及时的背景操作怎么办?
让我们说你有一个在后台线程上运行的简单操作.您希望提供一种取消此操作的方法,以便创建一个布尔标志,您可以从取消按钮的单击事件处理程序设置为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循环中的读取,因为它认为它无法修改该值.
哪种方法正确?(为什么?)
[编辑:在这方面似乎有两个明确定义和对立的思想流派.我正在寻找一个明确的答案,所以请尽可能发布您的理由并引用您的消息来源和您的答案.]
我主要关心的是布尔标志......没有任何同步就可以安全地使用它吗?我在几个地方读到它是原子的(包括文档).
class MyTask
{
private ManualResetEvent startSignal;
private CountDownLatch latch;
private bool running;
MyTask(CountDownLatch latch)
{
running = false;
this.latch = latch;
startSignal = new ManualResetEvent(false);
}
// A method which runs in a thread
public void Run()
{
startSignal.WaitOne();
while(running)
{
startSignal.WaitOne();
//... some code
}
latch.Signal();
}
public void Stop()
{
running = false;
startSignal.Set();
}
public void Start()
{
running = true;
startSignal.Set();
}
public void Pause()
{
startSignal.Reset();
}
public void Resume()
{
startSignal.Set();
}
} …Run Code Online (Sandbox Code Playgroud) (这是重复:如何正确读取Interlocked.Increment'ed int字段?但是,在阅读了答案和评论之后,我仍然不确定正确的答案.)
有些代码我不拥有,也无法更改为使用在几个不同线程中增加int计数器(numberOfUpdates)的锁.所有通话都使用:
Interlocked.Increment(ref numberOfUpdates);
Run Code Online (Sandbox Code Playgroud)
我想在我的代码中读取numberOfUpdates.既然这是一个int,我知道它不会撕裂.但是,确保我获得最新价值的最佳方法是什么?看起来我的选择是:
int localNumberOfUpdates = Interlocked.CompareExchange(ref numberOfUpdates, 0, 0);
Run Code Online (Sandbox Code Playgroud)
要么
int localNumberOfUpdates = Thread.VolatileRead(numberOfUpdates);
Run Code Online (Sandbox Code Playgroud)
两者都有效(无论优化,重新排序,缓存等,都可以提供最新的价值)?一个比另一个更受欢迎吗?还有第三种选择更好吗?
Joe Albahari有一个关于多线程的伟大系列,这是一本必须阅读的内容,对于任何进行C#多线程处理的人来说都应该被人们所熟知.
然而,在第4部分中,他提到了易变的问题:
请注意,应用volatile不会阻止写入后读取交换,这可以创建脑筋急转弯.Joe Duffy通过以下示例很好地说明了这个问题:如果Test1和Test2在不同的线程上同时运行,则a和b最终都可能为0(尽管在x和y上都使用了volatile)
接下来是MSDN文档不正确的说明:
MSDN文档指出使用volatile关键字可确保始终在字段中显示最新值.这是不正确的,因为正如我们所见,可以重新排序读取后跟读取.
我查看了MSDN文档,该文档最后一次在2015年更改但仍列出:
volatile关键字表示某个字段可能被同时执行的多个线程修改.声明为volatile的字段不受编译器优化的约束,这些优化假定由单个线程进行访问.这可确保始终在字段中显示最新值.
现在我仍然避免使用volatile来支持使用陈旧数据的更冗长的线程:
private int foo;
private object fooLock = new object();
public int Foo {
get { lock(fooLock) return foo; }
set { lock(fooLock) foo = value; }
}
Run Code Online (Sandbox Code Playgroud)
关于多线程的部分是在2011年写的,这个论点今天仍然有效吗?是否应该不惜一切代价避免使用volatile或完全内存防护,以防止引入非常难以产生的错误,如上所述甚至依赖于它运行的CPU供应商?
我已经使用了volatile,我不确定它是否有必要.我很确定锁定在我的情况下会有点过分.阅读这个帖子(Eric Lippert评论)让我对使用volatile感到焦虑:什么时候应该在c#中使用volatile关键字?
我使用volatile是因为我的变量在多线程上下文中使用,其中可以同时访问/修改此变量,但是我可以在没有任何伤害的情况下松散添加(参见代码).
我添加"挥发性",以确保有不发生未命中对准:在另一个取可以在2通过在从另一个线程中间的写入被打破仅读取32可变的比特和其他32位.
我先前的假设(先前的陈述)是否真的可以发生?如果没有,"挥发性"使用是否仍然是必要的(选项属性修改可能在任何线程中发生).
看了2个第一个答案.我想坚持代码编写方式的事实,如果由于并发性我们错过了一个增量(想要从2个线程递增但结果仅由于并发而增加1)并不重要变量'_actualVersion'递增.
作为参考,这是我正在使用它的代码的一部分.仅在应用程序空闲时报告保存操作(写入磁盘).
public abstract class OptionsBase : NotifyPropertyChangedBase
{
private string _path;
volatile private int _savedVersion = 0;
volatile private int _actualVersion = 0;
// ******************************************************************
void OptionsBase_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
_actualVersion++;
Application.Current.Dispatcher.BeginInvoke(new Action(InternalSave), DispatcherPriority.ApplicationIdle);
}
// ******************************************************************
private void InternalSave()
{
if (_actualVersion != _savedVersion)
{
_savedVersion = _actualVersion;
Save();
}
}
// ******************************************************************
/// <summary>
/// Save Options
/// </summary>
private void Save()
{
using (XmlTextWriter writer = new XmlTextWriter(_path, null)) …Run Code Online (Sandbox Code Playgroud) 像许多其他人一样,我一直对易失性读/写和围栏感到困惑.所以现在我想完全理解这些是做什么的.
因此,易失性读取应该(1)表现出获取语义,(2)保证读取的值是新鲜的,即它不是缓存值.让我们关注(2).
现在,我已经读过,如果你想执行一个易失性读取,你应该在读取后引入一个获取栅栏(或一个完整的栅栏),如下所示:
int local = shared;
Thread.MemoryBarrier();
Run Code Online (Sandbox Code Playgroud)
这究竟是如何阻止读取操作使用以前缓存的值?根据栅栏的定义(不允许读取/存储在栅栏上方/下方移动),我会在读取之前插入栅栏,防止读取穿过栅栏并及时向后移动(也就是说,是缓存).
如何防止读取被及时向前移动(或后续指令被及时向后移动)保证了易失性(新鲜)读取?它有什么用?
类似地,我认为易失性写入应该在写入操作之后引入栅栏,从而阻止处理器及时向前移动写入(也就是说,延迟写入).我相信这会使处理器刷新对主存储器的写入.
但令我惊讶的是,C#实现在写入之前引入了栅栏!
[MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations
public static void VolatileWrite(ref int address, int value)
{
MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way.
address = value;
}
Run Code Online (Sandbox Code Playgroud)
更新
根据这个例子,显然是从"坚果壳中的C#4"中取出的,在写入之后放置的栅栏2 应该强制写入立即刷新到主存储器,并且在读取之前放置的栅栏3 应该保证一个新的阅读:
class Foo{
int _answer;
bool complete;
void A(){
_answer = 123;
Thread.MemoryBarrier(); …Run Code Online (Sandbox Code Playgroud)