我看了一些指南,但没有一个人把我带到那里。我从来没有做过线程,讨论了一个帖子,或者在杂货店看过一个帖子,所以这可能是个问题。目前。我在尝试:
private void btnHUp_MouseDown(object sender, MouseEventArgs e)
{
{
ThreadStart HUp = new ThreadStart(dothis);
t = new Thread(HUp);
t.Start();
}
}
public void dothis()
{
if (intHour < 23)
intHour = intHour += intStep;
lblTimerHour.Text = intHour.ToString("00");
}
private void btnHUp_MouseUp(object sender, MouseEventArgs e)
{
t.Abort();
}
}
这让我在
上未处理InvalidOperationException lblTimerHour.Text = intHour.ToString("00");
线。我读到了这意味着什么......也可能是普通话,我得到了一般概念 - 它是什么问题,但它很痛苦。如果你问我修理它的第一步,我就会像前灯中的鹿一样看着你。我们还没有在课堂上走得那么远。
答案 0 :(得分:3)
这里的问题是您尝试更新的标签由主线程拥有(即UI运行的内容),这意味着只有该线程才能访问/更新它。因此,由于您处于不同的线程中,您需要告诉UI线程为您更新标签。
这样的事情会起作用:
Action updateLabel = () => lblTimerHour.Text = intHour.ToString("00");
lblTimerHour.BeginInvoke(updateLabel);
这样做是告诉lblTimerHour调用你在上面定义的动作(updateLabel)。
答案 1 :(得分:2)
请参阅此帖子:How to update the GUI from another thread in C#?
lblTimerHour.Invoke((MethodInvoker)delegate {
//Do what you need to do with the label
lblTimerHour.Text = intHour.ToString("00");
});
这应该可以解决问题:
public void dothis()
{
do
{
if (intHour < 23)
intHour = intHour += intStep;
lblTimerHour.Invoke((MethodInvoker)delegate {
//Update the label from the GUI thread
lblTimerHour.Text = intHour.ToString("00");
});
//Pause 1 sec. Won't freeze the gui since it's in another thread
System.Thread.Sleep(1000);
}while(true); //Thread is killed on mouse up
}
答案 2 :(得分:1)
好吧,让我们来看看你已经拥有的东西。
首先,我看到你这样做了。
private void btnHUp_MouseDown(object sender, MouseEventArgs e)
{
ThreadStart HUp = new ThreadStart(dothis);
t = new Thread(HUp);
t.Start();
}
虽然这肯定不是最新鲜的东西,它仍然可以工作。如果你想要一些更新鲜的食材,那么你可能会选择它。
private void btnHUp_MouseDown(object sender, MouseEventArgs e)
{
Task.Factory.StartNew(dothis);
}
其次,我看到了这一点。
public void dothis()
{
if (intHour < 23) intHour = intHour += intStep;
lblTimerHour.Text = intHour.ToString("00");
}
这里的问题是您正在尝试从主UI线程以外的线程更新UI控件。您会看到UI控件具有所谓的线程关联。只能从创建它们的线程访问它们。你所拥有的将导致各种不可预测的问题,包括在时空中撕裂整体。
更好的选择是做到这一点。
public void dothis()
{
while (intHour < 23)
{
intHour = intHour += intStep;
lblTimerHour.Invoke((Action)(
() =>
{
lblTimerHour.Text = intHour.ToString("00");
}));
}
}
我以为你错过了循环所以我添加了它。虽然我不能说我个人喜欢这种东西,但吞下去容易得多。这里真正的问题是工作线程确实没有做很多有用的工作。最重要的是,我们必须使用一个笨拙的编组操作将结果传回UI线程。它不漂亮,但它会起作用。
最后,这让我想到了这一点。
private void btnHUp_MouseUp(object sender, MouseEventArgs e)
{
t.Abort();
}
您试图中止一个非常不可取的线程。问题是它在不可预测的时间从线程中控制。该线程可能处于写入数据结构的中间,这会破坏它。这实际上是一个非常糟糕的问题,因为从调用堆栈中的任何一个帧操作的过程中的任何数据结构都可能处于不一致状态。这包括你没写的代码。这就是为什么通过这样做很难说出你可能会或可能不会腐败的原因。
您需要考虑的是使用合作取消机制。这包括使用CancellationTokenSource
和CancellationToken
。一旦我们把所有东西放在一起,这就是它的样子。
private CancellationTokenSource cts = null;
private void btnHUp_MouseDown(object sender, MouseEventArgs e)
{
cts = new CancellationTokenSource();
Task.Factory.StartNew(() => dothis(cts.Token));
}
private void btnHUp_MouseUp(object sender, MouseEventArgs e)
{
cts.Cancel();
}
public void dothis(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
intHour += intStep;
lblTimerHour.Invoke((Action)(
() =>
{
lblTimerHour.Text = intHour.ToString("00");
}));
Thread.Sleep(1000);
}
}
这样做表明工作线程应该自己正常关闭。这使得工作者线程有机会在最终终止之前整理一些东西。
答案 3 :(得分:1)
如果您想每X个时间段更新一次用户界面,那么现有的工具已经存在;一个Timer
将完全按照你的意愿行事,而且编码比创建一个只花费大部分时间小睡的新线程更有效,也更容易。此外,中止线程是一个非常坏的标志。不惜一切代价避免它。
首先创建计时器并在构造函数中配置它:
private System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
private int hour = 0;
private int step = 0;
public Form1()
{
InitializeComponent();
timer.Tick += timer_Tick;
timer.Interval = 1000;
}
让Tick
事件做任何事情都应该做的事情。
private void timer_Tick(object sender, EventArgs e)
{
if (hour < 23)
{
hour += step;
lblTimerHour.Text = hour.ToString("00");
}
}
然后只需启动计时器就可以开始计时,并在希望计时器停止时停止计时器:
private void btnHUp_MouseDown(object sender, MouseEventArgs e)
{
timer.Start();
}
private void btnHUp_MouseUp(object sender, MouseEventArgs e)
{
timer.Stop();
}
计时器将自动确保Tick
事件处理程序在UI线程中运行,并且在等待下一个事件发生时它不会阻止UI线程(或任何其他线程),它将什么都不做。