我的winForms中有Timer
,当TimeSpan
为0
时,我将done
设置为true
并尝试检查线程的值。
我的代码:
初始变量:
private DateTime endTime = DateTime.UtcNow.AddMinutes(1);
private bool done;
private bool running = true;
private int count = 0;
private delegate void setLabelMethod(string msg);
private void setLabelValue(string msg) { label2.Text = msg; }
计时器处理:
private void timer1_Tick_1(object sender, EventArgs e)
{
TimeSpan remainingTime = endTime - DateTime.UtcNow;
if (remainingTime <= TimeSpan.Zero)
{
label1.Text = "Done!";
timer1.Enabled = false;
done = true;
running = false;
}
else
{
//...
}
}
线程回调函数:
private void test()
{
do
{
if (done)
{
Invoke(new setLabelMethod(setLabelValue), "yeah");
done = false;
}
Thread.Sleep(500);
} while (running);
}
开始执行timer1_Tick
和Thread
。
private void button2_Click(object sender, EventArgs e)
{
Thread th = new Thread(new ThreadStart(test));
th.Start();
timer1.Enabled = true;
}
问题是.test()
方法中的以下陈述不是true
,为什么?
语句
if (done)
{
Invoke(new setLabelMethod(setLabelValue), "yeah");
done = false;
}
有人可以指出我的错误吗?提前致谢。
答案 0 :(得分:2)
您没有synchronization保护'done'变量。当一个对象或变量可以在一个线程中访问而另一个线程正在或可能正在修改它时,需要某种形式的同步。
实际上失败的方式很多。例如,考虑这个循环:
private void test()
{
do
{
if (done)
{
Invoke(new setLabelMethod(setLabelValue), "yeah");
done = false;
}
Thread.Sleep(500);
} while (running);
}
假设编译器知道Thread.Sleep
不会修改done
或running
。可以得出结论,此循环中没有任何内容修改done
或running
(假设输入循环时done
为假),因此它可以缓存done
(和{{1调用running
的调用寄存器。
换句话说,它可以“优化”你的循环:
Thread.Sleep
请注意,如果private void test()
{
if (done)
Invoke(new setLabelMethod(setLabelValue), "yeah");
done = false;
if (running)
while(1) Thread.Sleep(500);
}
和done
的值未被其他线程更改,则这是真正的优化。编译器可以自由地假设这一点,因为它违反了修改一个线程中的值的规则,而另一个线程正在或可能正在访问它而没有同步,这个线程可能正在访问这些变量,并且此代码不包含同步函数。
当然,如果您遵守规则,您的代码不必担心这一点。禁止编译器进行破坏代码的“优化”,而不必担心它是如何做到的。但这只适用于遵守规则的情况。 (实际上,编译器不会通过对同步函数的调用来缓存寄存器中的值,并且volatile变量永远不会缓存在寄存器中。)
答案 1 :(得分:1)
正如Martin James所提到的那样,你应该使用而不是投票标志 线程同步机制。
这个简单的示例显示了如何使用Monitor.Wait
,Monitor.Pulse
和lock
执行此操作。
此代码与您的代码之间的重要区别在于,会阻止线程运行,直到满足条件,因此提高 性能强大的代码。
class Program
{
static void Main(string[] args)
{
ThreadExample example = new ThreadExample();
Thread thread = new Thread(example.Run);
Console.WriteLine("Main: Starting thread...");
thread.Start();
Console.WriteLine("Press a key to send a pulse");
Console.ReadKey();
lock (example) //locks the object we are using for synchronization
{
Console.WriteLine("Sending pulse...");
Monitor.Pulse(example); //Sends a pulse to the thread
Console.WriteLine("Pulse sent.");
}
thread.Join();
Console.ReadKey();
}
}
class ThreadExample
{
public void Run()
{
Console.WriteLine("Thread: Thread has started");
lock (this) //locks the object we are using for synchronization
{
Monitor.Wait(this); //Waits for one pulse - thread stops running until a pulse has been sent
Console.WriteLine("Thread: Condition has been met");
}
}
}
要修改代码以使用此机制,您需要维护对用于启动线程的对象的引用(在此示例中,我将其称为threadObject
)
private void timer1_Tick_1(object sender, EventArgs e)
{
TimeSpan remainingTime = endTime - DateTime.UtcNow;
if (remainingTime <= TimeSpan.Zero)
{
label1.Text = "Done!";
timer1.Enabled = false;
lock(threadObject){
Monitor.Pulse(threadObject); //You signal the thread, indicating that the condition has been met
}
}
else
{
//...
}
}
然后,在您的test()
方法中,您只需要这个:
private void test()
{
lock(this)
{
Monitor.Wait(this); //Will stop the thread until a pulse has been recieved.
Invoke(new setLabelMethod(setLabelValue), "yeah");
}
}
答案 2 :(得分:0)
在timer1_Tick
方法中,您设置了running = false;
- 并且您的线程依赖于此变量来循环。据我所见,这用于在条件满足后停止线程。
您可以将此语句移至您的主题:
private void test()
{
do
{
if (done)
{
Invoke(new setLabelMethod(setLabelValue), "yeah");
done = false;
running = false;
}
Thread.Sleep(500);
} while (running);
}
这样可以确保在满足条件时运行线程代码。可能发生的情况是,当if (done)
代码设置timer1_Tick
从而停止线程时,线程已经超过running = false;
语句。从running = false;
方法中删除timer1_Tick
语句。
答案 3 :(得分:0)
你的线程花费99.9999%的时间休眠,然后它测试'running'标志,如果是false则退出然后测试'done'标志,然后再次休眠。计时器处理程序设置'done'标志,然后清除'running'标志。当计时器最终触发时,线程会发现“运行”标志被清除,因此在它检查“完成”标志之前退出是很可能的。尝试在设置'done'标志和清除'running'标志之间设置一个睡眠(1000)。然后尝试一种比轮询标志更好的信令机制!