在窗体句柄创建之前,不能在控件上调用Invoke或BeginInvoke
本文关键字:调用 Invoke BeginInvoke 控件 句柄 窗体 创建 不能 | 更新日期: 2023-09-27 18:01:32
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace try1
{
public partial class Form1 : Form
{
volatile bool start_a = false;
volatile bool start_b = false;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (start_a == false)
{
button1.Text = "Running";
start_a = true;
Thread thread2 = new Thread(new ThreadStart(th1));
thread2.Start();
}
else
{
button1.Text = "Click to start";
start_a = false;
}
}
void th1()
{
int a=0;
while (start_a==true)
{
label1.Invoke((MethodInvoker)(() => label1.Text = Convert.ToString(a)));
Thread.Sleep(50);
a++;
}
}
void th2()
{
int b = 0;
while (start_b == true)
{
label2.Invoke((MethodInvoker)(() => label2.Text = Convert.ToString(b)));
Thread.Sleep(5000);
b=b+5;
}
}
private void button2_Click(object sender, EventArgs e)
{
if (start_b == false)
{
button2.Text = "Running";
start_b = true;
Thread thread2 = new Thread(new ThreadStart(th2));
thread2.Start();
}
else
{
button2.Text = "Click to start";
start_b = false;
}
}
private void quitting(object sender, FormClosingEventArgs e)
{
start_a = false;
start_b = false;
}
}
}
我们需要更多关于错误发生的地点和时间的详细信息。通过查看代码,我的第一个猜测是,您在试图关闭表单时遇到了异常。退出事件处理程序将start_a
和start_b
设置为false,但不会等到后台线程完成后才让表单运行任何清理代码。现在在后台线程和表单清理之间有了一个竞争条件。这段清理代码释放了窗口句柄,所以当后台线程在5秒后醒来,并可能试图调用文本更改回UI线程时,你就会失败。
解决这个问题的最简单的方法是Join()任何活动的后台线程,并等待他们完成之前,你让表单完成关闭。一种更正确、更复杂的方法是设置一个适当的线程同步原语(Mutex、WaitHandle、sempahor等),以允许您发出信号让线程立即停止。
没有简单的解决方案,因为你必须与其他线程同步,但是调用请求在UI线程中执行,这个线程应该关闭其他线程!所以tUI要求t1 t2退出,但是t1 t2可能需要tUI退出!:)
为quitting
方法添加Application.DoEvents();
(read =process all invoke requests):
private void quitting(object sender, FormClosingEventArgs e)
{
start_a = false;
start_b = false;
Application.DoEvents(); // NOT the solution, is not enough!!!
}
对大多数竞态条件进行排序,但还不够。
为什么?因为这个可能的,但非常不可能的竞争条件:
t1 before queuing Invoke
~~~~~~~>
start_a = false; start_b= false; Application.DoEvents();
<~~~~~~~
t1 queue an Invoke
~~~~~~~> (very improbable but possible)
(continue trough disposing)
<~~~~~~~
queued Invoke on disposed label -> crash!
锁定检查start变量状态和清空消息队列的临界区应该可以解决问题。你的练习:找到其他可能的竞争条件,在最坏的情况下找到一种方法在5秒内退出(提示:不要使用睡眠)。睡眠是魔鬼)。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
object _closing1;
object _closing2;
volatile bool start_a = false;
volatile bool start_b = false;
public Form1()
{
InitializeComponent();
button1.Text = "Click to start";
button2.Text = "Click to start";
_closing1 = new object();
_closing2 = new object();
}
private void button1_Click(object sender, EventArgs e)
{
if (start_a == false)
{
button1.Text = "Running";
start_a = true;
Thread thread2 = new Thread(new ThreadStart(th1));
thread2.Start();
}
else
{
button1.Text = "Click to start";
start_a = false;
}
}
void th1()
{
int a = 0;
while (true)
{
lock (_closing1)
{
if (start_a == false)
break;
label1.BeginInvoke((MethodInvoker)(() => label1.Text = Convert.ToString(a)));
}
Thread.Sleep(50);
a++;
}
}
void th2()
{
int b = 0;
while (true)
{
lock (_closing2)
{
if (start_b == false)
break;
label2.BeginInvoke((MethodInvoker)(() => label2.Text = Convert.ToString(b)));
}
Thread.Sleep(5000);
b = b + 5;
}
}
private void button2_Click(object sender, EventArgs e)
{
if (start_b == false)
{
button2.Text = "Running";
start_b = true;
Thread thread2 = new Thread(new ThreadStart(th2));
thread2.Start();
}
else
{
button2.Text = "Click to start";
start_b = false;
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
lock (_closing1)
{
start_a = false;
// Clear the message queue now so access on disposed lables is possible.
// No more invokes will be queued because 1) start_a = false
// 2) t1 is out of the critical section
Application.DoEvents();
}
lock (_closing2)
{
start_b = false;
// Clear the message queue now so access on disposed lables is possible.
// No more invokes will be queued because 1) start_b = false
// 2) t2 is out of the critical section
Application.DoEvents();
}
}
}
}
我认为问题是你正在更新一个线程上的UI控件,而不是在它被创建的线程上;我认为你应该看看这个:如何从c#中的另一个线程更新GUI ?或者在这里:http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx一些例子比需要的更复杂,但它的要点是,你必须从同一线程更新控件,它被创建在;控制。