在工作中碰到了,在.Net C#窗体开发中怎么创建多线程(子线程),并且从子线程访问UI主线程,修改UI上的控件显示,我在此记录一下。
在对我们公司购买的线性激光仪器进行测试的过程中,原来的测试软件能够很好的对仪器进行各种数值的测试,最后能在屏幕上显示出来。但是领导要求我修改原代码使之能够同时进行6个测试。我马上想到了开6个线程。在经过对原代码的阅读后,我看到原来代码是在点击“开始”按钮后,启动一个子线程进行测试的:
private void buttonStart_Click(object sender, EventArgs e)
{
Thread testAtinyThread1 = new Thread(testAtiny);
testAtinyThread1.Start();
}
并且有一个专门的子程序进行对UI窗体上的控件更新。
private void DisplayResult(object obj)
{
labelHour.Text = totalSpan.TotalHours.ToString("F0");
labelMinute.Text = totalSpan.Minutes.ToString();
labelSecond.Text = totalSpan.Seconds.ToString();
labelTotalConnectNum.Text = TotalFrameCount.ToString();
labelConnectFailNum.Text = FailedFrameCount.ToString();
labelConnectFailRate.Text = ((FailedFrameCount * 100.0) / (TotalFrameCount * 1.0)).ToString("F2");
labelDataExceptionNum.Text = DataExceptionCount.ToString();
labelDataExceptionRate.Text = ((DataExceptionCount * 100.0) / (TotalFrameCount * 1.0)).ToString("F2");
labelSuccessConnNum.Text = (TotalFrameCount - FailedFrameCount).ToString();
labelStdDev.Text = stdDeviation.ToString();
labelAvgDev.Text = Avg.ToString("F3");
labelCurrentSpan.Text = currentSpan.Milliseconds.ToString();
labelAvgSpan.Text = (totalSpan.TotalMilliseconds / TotalFrameCount).ToString("F0");
}
其中是通过下面这条语句通知UI进行更新的:
SynchronizationContext syncContext;
syncContext.Post(DisplayResult, null);
它也是在一个while循环语句中:
while (enableTest)
{
CheckData(ref frameData);
DateTime currentTime = DateTime.Now;
totalSpan = currentTime.Subtract(startTime);
totalSpan = currentTime.Subtract(startTime);
currentSpan = currentTime.Subtract(lastTime);
lastTime = currentTime;
syncContext.Post(DisplayResult, null);
Console.WriteLine(myID + ", displayresult");
}
而与线性激光器的通讯是通过ip地址访问。
myIp = textBox_IP.Text;
//MessageBox.Show("myIp="+myIp);
ErrorCode errorCode;
errorCode = GBLMDevice.SetDeviceTriggerMode(myIp, false);
我最初想就new6个窗体,每个窗体就是原来代码的窗体,并且对应不同的ip地址不就行了?
private void button_Start_Click(object sender, EventArgs e)
{
Form1 form1 = new Form1(1);
form1.Show();
Form1 form2 = new Form1(2);
form2.Show();
Form1 form3 = new Form1(3);
form3.Show();
Form1 form4 = new Form1(4);
form4.Show();
}
我这么修改后,6个窗体确实会继续测试,但是每个窗体上的数字跳动的很卡,而且有“串”在一起的感觉。
那么我接下来就把6个窗体整合到一个窗体上:
在代码中启动了6个子线程,每个子线程还是通过原来的方法,
syncContext.Post(DisplayResult, null);
与UI窗体沟通。
我这么做后,看到明显的6个测试数值串在一起,我看了代码后知道我还是通过全局变量来进行测试,那么测试数值当然串在一起了。
public partial class Form1 : Form
{
bool enableTest = false;
//private string myIp = "192.168.2.3";
private string myIp;
private byte myFrameCnt = 0;
//private bool myReady = false;
const int frameSize = 1280;
float[,] frameData = new float [frameSize, 3];
private int TotalFrameCount = 0; //总连接次数
private int FailedFrameCount = 0; //失败连接次数
private double Avg = 0; //偏差均值
private double stdDeviation; //数据偏差均方差
private int DataExceptionCount = 0; //数据异常次数
TimeSpan totalSpan;
TimeSpan currentSpan;
那么我就准备建立Test类来,把这些变量放到Test类内,作为属性,那么测试数据就不会串在一起了。
public Test test1;
public Test test2;
public Test test3;
public Test test4;
public Test test5;
public Test test6;
public class Test
{
public bool enableTest = false;
public string myIp;
private byte myFrameCnt = 0;
const int frameSize = 1280;
float[,] frameData = new float[frameSize, 3];
private int TotalFrameCount = 0; //总连接次数
private int FailedFrameCount = 0; //失败连接次数
private double Avg = 0; //偏差均值
private double stdDeviation; //数据偏差均方差
private int DataExceptionCount = 0; //数据异常次数
TimeSpan totalSpan;
TimeSpan currentSpan;
public Test()
{
}
线程的建立是这样的:
private void button_Start_Click_1(object sender, EventArgs e)
{
test1 = new Test();
test2 = new Test();
test3 = new Test();
test4 = new Test();
test5 = new Test();
test6 = new Test();
if (checkBox1.Checked)
{
test1.myIp = "192.168.2.1";
test1.enableTest = true;
Thread thread = new Thread(new ThreadStart(test1.testAtiny1));
thread.Start();
}
if (checkBox2.Checked)
{
test2.myIp = "192.168.2.2";
test2.enableTest = true;
Thread thread2 = new Thread(new ThreadStart(test2.testAtiny1));
thread2.Start();
}
if (checkBox3.Checked)
{
test3.myIp = "192.168.2.3";
test3.enableTest = true;
Thread thread3 = new Thread(new ThreadStart(test3.testAtiny1));
thread3.Start();
}
if (checkBox4.Checked)
{
test4.myIp = "192.168.2.4";
test4.enableTest = true;
Thread thread4 = new Thread(new ThreadStart(test4.testAtiny1));
thread4.Start();
}
if (checkBox5.Checked)
{
test5.myIp = "192.168.2.5";
test5.enableTest = true;
Thread thread5 = new Thread(new ThreadStart(test5.testAtiny1));
thread5.Start();
}
if (checkBox6.Checked)
{
test6.myIp = "192.168.2.6";
test6.enableTest = true;
Thread thread6 = new Thread(new ThreadStart(test6.testAtiny1));
thread6.Start();
}
}
然而我发现UI界面的控件却始终不能更新。
在经过网上资料的查询,采用了静态的Form2的声明,并且下面这条语句能够使控件更新。
public partial class Form2 : Form
{
public static Form2 form2;
public Form2()
{
InitializeComponent();
form2 = this;
}
public void Form2_Load(object sender, EventArgs e)
{
}
private void LabelHourSetText(string s)
{
if (Form2.form2.labelHour.InvokeRequired)
{
Form2.form2.labelHour.Invoke(new Action(() => {
Form2.form2.labelHour.Text = (string)s;
}));
}
else
{
Form2.form2.labelHour.Text = (string)s;
}
}
while循环经过了线程睡眠的处理:
while (enableTest)
{
CheckData(ref frameData);
DateTime currentTime = DateTime.Now;
totalSpan = currentTime.Subtract(startTime);
totalSpan = currentTime.Subtract(startTime);
currentSpan = currentTime.Subtract(lastTime);
lastTime = currentTime;
if (myIp == "192.168.2.1")
Display();
if (myIp == "192.168.2.2")
Display2();
if (myIp == "192.168.2.3")
Display3();
if (myIp == "192.168.2.4")
Display4();
if (myIp == "192.168.2.5")
Display5();
if (myIp == "192.168.2.6")
Display6();
Thread.Sleep(100);
}
这样数据能不串的正常跳动,原来的要求也基本达到了。