有没有更好的方法来实现像下面这样的简单锁定?
如果它还没有被运行,我只想要“DOSOMETHING”。我应该在这里使用reall锁吗?如果我使用锁定会导致一切排队并等待锁定释放? (那不是我想要的!)
由于
bool running = false;
void DataDisplayView_Paint(object sender, PaintEventArgs e)
{
// if (!this.initialSetDone)
if (!running)
{
this.running = true;
//DOSOMETHING
this.running = false;
}
}
答案 0 :(得分:5)
不,你不想在这里使用锁。这不是线程同步问题。这是一种方法重入问题。
您可以尝试这样的事情。
bool running = false;
void DataDisplayView_Paint(object sender, PaintEventArgs e)
{
if (!this.running)
{
this.running = true;
try
{
//DOSOMETHING
}
finally
{
this.running = false;
}
}
}
答案 1 :(得分:2)
您只需要同步(锁定是最简单的方法)位代码:
bool running = false;
readonly object padlock = new object();
void DataDisplayView_Paint(object sender, PaintEventArgs e)
{
if (!this.initialSetDone)
{
lock(padlock)
{
if(running) return;
running = true;
}
try {
//DOSOMETHING
}
finally
{
lock(padlock)
{
this.running = false;
}
}
}
}
答案 2 :(得分:1)
最好的方法是使用try / finally块
try {
this.running = true;
...
} finally {
this.running = false;
}
只有从多个线程调用此方法时才需要真正的线程锁。鉴于它似乎是一个paint事件处理程序,因此不太可能将控件与单个线程关联起来。
答案 3 :(得分:1)
我错过了什么吗?您发布的代码似乎没有做任何事情。也就是说,无论running
是否为真,代码都会运行。
通常,任何尝试像这样“锁定”自己的代码......
if (!running)
{
running = true;
try
{
// This code should not call itself recursively.
// However, it may execute simultaneously on more than one thread
// in very rare cases.
}
finally
{
running = false;
}
}
...非常好,只要你处于单线程场景中。如果您正在运行多线程代码,则会出现问题,因为您假设没有两个线程同时到达if (!running)
行。
多线程代码中的解决方案是使用某种形式的原子开关。我已将AutoResetEvent
用于此目的:
var ready = new AutoResetEvent(true);
if (ready.WaitOne(0))
{
try
{
// This code will never be running on more than one thread
// at a time.
}
finally
{
ready.Set();
}
}
答案 4 :(得分:1)
请注意,如果您对油漆回调进行了重新考虑,那么您会遇到更严重的问题。油漆处理程序应该阻止你的消息泵(并且应该相对快速地完成),所以你永远不应该看到这种情况。唯一的例外是你从paint处理器的某个地方调用Application.DoEvents(),你真的应该这样做。
答案 5 :(得分:0)
如果您使用Monitor.TryEnter,则可以指定超时,在这种情况下,您获得的结果是:
如果您没有提供超时,或将超时设置为0,则此呼叫不会阻止并立即返回(可能更符合您的要求?):
if (!this.initialSetDone && Monitor.TryEnter(_lock))
{
// DOSOMETHING
}
或者,您可以将running
变量设为volatile,以便始终获得存储在变量中的最新值:
private volatile bool running;
if (!this.initialSetDone && !this.running) // #1
{
this.running = true;
try
{
// DOSOMETHING
}
finally
{
this.running = false;
}
}
第二种方法不会排队后续调用,但是有可能两个线程都会命中#1并评估它是否可以继续运行然后最终都运行DOSOMETHING,尽管它不太可能。
答案 6 :(得分:0)
你在中间移动了可变名称,所以我假设你想要:
bool running = false;
void DataDisplayView_Paint(object sender, PaintEventArgs e)
{
if (!this.running)
{
this.running = true;
//DOSOMETHING
this.running = false;
}
}
你遇到的问题是,如果可以从多个线程调用DataDisplayView_Paint,那么if (!this.running)
和this.running = true;
之间的另一个线程可能会跳入并启动DOSOMETHING(因为正在运行)仍然是假的)。然后第一个线程将恢复,并再次启动DOSOMETHING。如果这是可能的,那么你将需要使用真正的锁。
答案 7 :(得分:0)
我只想要“DOSOMETHING” 它尚未运行
您的问题没有足够的信息,所以我不禁对您的代码做出假设。
我的第一个假设是,基于签名DataDisplayView_Paint(object s, PaintEventArgs e)
,您的代码在GUI线程上运行。
我的第二个假设是您的代码DOSOMETHING
是同步的。
考虑到这一点,这里的代码版本保证我们只运行DOSOMETHING
,如果它尚未运行:
void DataDisplayView_Paint(object s, PaintEventArgs e)
{
//DOSOMETHING
}
GUI线程一次只处理一条消息,DataDisplayView_Paint
方法在DOSOMETHING
完成之前不会退出。如果你正在使用GUI做任何事情,比如绘制到Graphics对象或更改标签,那么这个代码将不会从多个线程调用 - 如果是这样,.NET将抛出异常。换句话说,不需要任何同步。
让我们假设DOSOMETHING
异步运行 - 现在我们有一个有趣的问题,但它很容易解决,而且你不需要任何bool。
基本上,您所做的只是在DOSOMETHING运行时禁用事件处理程序,然后重新启用它。而不是使用bool,根据需要取消挂钩并重新挂钩您的事件处理程序:
void DataDisplayView_Paint(object s, PaintEventArgs e)
{
DataDisplayView.Paint -= DataDisplayView_Paint;
DoSomethingAsynchronously(); // re-hooks event handler when completed
}
void DoSomethingAsychronously()
{
ThreadPool.QueueUserWorkItem(() =>
{
try
{
// DOSOMETHING
}
finally
{
// may need a lock around this statement
DataDisplayView.Paint += DataDisplayView_Paint;
}
});
}